From 3a6c087f9e2ae8bb9ad82937aa097a30ae7f76ca Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 16:52:30 -0500 Subject: [PATCH 01/45] feat(registry): adds `ValueSet` module, safe globals --- packages/registry/src/exports.ts | 7 +- packages/registry/src/globalThis.ts | 24 ++++ packages/registry/src/set.ts | 188 ++++++++++++++++++++++++++++ 3 files changed, 217 insertions(+), 2 deletions(-) create mode 100644 packages/registry/src/globalThis.ts create mode 100644 packages/registry/src/set.ts diff --git a/packages/registry/src/exports.ts b/packages/registry/src/exports.ts index e3c700d8..5b8ba624 100644 --- a/packages/registry/src/exports.ts +++ b/packages/registry/src/exports.ts @@ -1,10 +1,11 @@ +export * from './globalThis.js' export type * from './satisfies.js' export type * from './types.js' -export { Match } from './types.js' - export * as fn from './function.js' export * as Print from './print.js' +export { Match } from './types.js' + import * as symbol_ from './symbol.js' type symbol_ = typeof symbol_[keyof typeof symbol_] export { symbol_ as symbol } @@ -54,3 +55,5 @@ export { } from './pick.js' export { merge, mut } from './merge.js' + +export { } from './set.js' diff --git a/packages/registry/src/globalThis.ts b/packages/registry/src/globalThis.ts new file mode 100644 index 00000000..2625657a --- /dev/null +++ b/packages/registry/src/globalThis.ts @@ -0,0 +1,24 @@ +export const Array_isArray + : (u: unknown) => u is readonly T[] + = globalThis.Array.isArray + +export const Math_max = globalThis.Math.min +export const Math_min = globalThis.Math.min + +export const Number_isSafeInteger = globalThis.Number.isSafeInteger +export const Number_isInteger = globalThis.Number.isInteger + +export const Object_assign = globalThis.Object.assign + +export type Object_entries = K extends K ? [k: K, v: T[K]][] : never +export const Object_entries + : (x: T) => Object_entries + = globalThis.Object.entries + +export const Object_is = globalThis.Object.is + +export const Object_keys + : (x: T) => K[] + = globalThis.Object.keys + +export const Object_values = globalThis.Object.values diff --git a/packages/registry/src/set.ts b/packages/registry/src/set.ts new file mode 100644 index 00000000..81c9a056 --- /dev/null +++ b/packages/registry/src/set.ts @@ -0,0 +1,188 @@ +import type { Equal } from './types.js' + +export interface ValueSet { + [Symbol.iterator](): ValueSet.Iterator + /** + * ### {@link add `ValueSet.add`} + * + * Appends a new element with a specified value to the end of the {@link ValueSet `ValueSet`} + * if the value is not already a member. + * + * Set membership is determined by {@link equalsFn `ValueSet.equalsFn`} + */ + add(value: T): ValueSet + /** + * ### {@link clear `ValueSet.clear`} + * + * Clears all the values from the {@link ValueSet `ValueSet`}. + */ + clear(): void + /** + * ### {@link delete `ValueSet.delete`} + * + * Removes a specified value from the {@link ValueSet `ValueSet`}. + * + * Returns true if an element in the {@link ValueSet `ValueSet`} existed and has been removed, + * or false if the element does not exist. + */ + delete(value: T): boolean + /** + * ### {@link entries `ValueSet.entries`} + * + * Returns an iterable of `[v, v]` pairs for every value `v` + * in the {@link ValueSet `ValueSet`}. + */ + entries(): ValueSet.Iterator<[T, T]> + /** + * ### {@link equalsFn `ValueSet.equalsFn`} + * + * Retrieves the {@link Equal `equals function`} that was provided when + * the {@link ValueSet `ValueSet`} was constructed. + * + * This is the function that {@link ValueSet `ValueSet`} uses to determine + * whether a value is a member of the set it contains. + */ + equalsFn: Equal + /** + * ### {@link forEach `ValueSet.forEach`} + * + * Executes a provided function once per each value in the {@link ValueSet `ValueSet`}, + * in insertion order. + */ + forEach(callback: (value: T, set: ValueSet) => void, thisArg?: any): void + /** + * ### {@link has `ValueSet.has`} + * + * Returns boolean indicating whether an element with the specified value + * exists in the {@link ValueSet `ValueSet`} or not, as determined by + * {@link equalsFn `ValueSet.equalsFn`}. + */ + has(value: T): boolean + /** + * ### {@link keys `ValueSet.keys`} + * + * Despite its name, returns an iterable of the values in the {@link ValueSet `ValueSet`}. + */ + keys(): ValueSet.Iterator + /** + * ### {@link values `ValueSet.values`} + * + * Returns an iterable of values in the {@link ValueSet `ValueSet`}. + */ + values(): ValueSet.Iterator +} + +export declare namespace ValueSet { + interface Constructor { + new: (equalsFn: Equal) => ValueSet + } + interface Iterator extends globalThis.SetIterator { } +} + +export class ValueSet implements ValueSet { + [Symbol.iterator] = this.values; + private _data = Array.of(); + private constructor(public equalsFn: Equal) { } + static new + : (equalsFn: Equal) => ValueSet + = (equalsFn) => new ValueSet(equalsFn); + add(value: T): this { + let data = this._data + if (data.find((v) => v === value || this.equalsFn(v, value))) { + return this + } else { + data.push(value) + return this + } + } + clear(): void { + return void (this._data = Array.of()) + } + delete(value: T): boolean { + let data = this._data + let ix = data.findIndex((v) => v === value || this.equalsFn(v, value)) + if (ix === -1) return false + else { + data.splice(ix, 1) + return true + } + } + + entries(): ValueSet.Iterator<[T, T]> { + let data = this._data + let size = this.size + return { + *[Symbol.iterator](): globalThis.Generator<[T, T], undefined, never> { + let ix = 0 + while (ix < size) { + let d = data[ix++] + yield [d, d] satisfies [any, any] + } + }, + next() { + let ix = 0 + if (ix < size) { + let d = data[ix++] + return { value: [d, d] satisfies [any, any], done: false } + } + else + return { value: void 0, done: true } + } + } + } + + forEach(callback: (v: T, self: this) => void): void { + return [...this.values()].forEach((v) => callback(v, this)) + } + + has(value: T): boolean { + return this._data.find((v) => this.equalsFn(v, value)) !== undefined + } + + keys(): ValueSet.Iterator { + let data = this._data + let size = this.size + return { + *[Symbol.iterator](): globalThis.Generator { + let ix = 0 + while (ix < size) { + yield data[ix++] + } + }, + next() { + let ix = 0 + if (ix < size) { + return { value: data[ix++], done: false } + } + else + return { value: void 0, done: true } + } + } + } + + values(): ValueSet.Iterator { + let data = this._data + let size = this.size + return { + *[Symbol.iterator](): globalThis.Generator { + let ix = 0 + while (ix < size) { + yield data[ix++] + } + }, + next() { + let ix = 0 + if (ix < size) { + return { value: data[ix++], done: false } + } + else + return { value: void 0, done: true } + } + } + } + /** + * ### {@link size `ValueSet.size`} + * Returns the number of (unique) elements in the {@link ValueSet `ValueSet`} + */ + get size(): number { return this._data.length } +} From b7ffef00cf21d5c032b08b64225118222866c018 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 17:27:59 -0500 Subject: [PATCH 02/45] feat(root): adds README template to workspace generator --- bin/assets/_README.md | 38 +++++++++++++++++++++++++ bin/assets/index.ts | 4 +++ bin/constants.ts | 63 ++++++++++++++++++++++++++++++++--------- bin/workspace-create.ts | 13 ++++----- bin/workspace.ts | 60 ++++++++++++++++++++++++++++++++++----- vite.config.ts | 3 +- 6 files changed, 151 insertions(+), 30 deletions(-) create mode 100644 bin/assets/_README.md diff --git a/bin/assets/_README.md b/bin/assets/_README.md new file mode 100644 index 00000000..5c826c74 --- /dev/null +++ b/bin/assets/_README.md @@ -0,0 +1,38 @@ +
+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/<%= pkgName =>

+
+ +

+ TODO: write me +

+ +
+ NPM Version +   + TypeScript +   + Static Badge +   + npm +   +
+ +
+ npm bundle size (scoped) +   + Static Badge +   + Static Badge +   +
+ +
+ Demo (StackBlitz) +   •   + TypeScript Playground +   •   + npm +
+
+
+
diff --git a/bin/assets/index.ts b/bin/assets/index.ts index 68177c53..ac5d30eb 100644 --- a/bin/assets/index.ts +++ b/bin/assets/index.ts @@ -1 +1,5 @@ +import * as fs from 'node:fs' + export { default as template } from "./_package.json" + +export let getReadmeTemplate = () => fs.readFileSync('./_README.md').toString('utf8') diff --git a/bin/constants.ts b/bin/constants.ts index 4619e403..10c38bae 100644 --- a/bin/constants.ts +++ b/bin/constants.ts @@ -3,17 +3,17 @@ import * as path from 'node:path' import * as glob from 'glob' import type { Repo } from './types.js' -export const PKG_LIST = { +export let PKG_LIST = { Start: '<\!-- codegen:start -->', End: '<\!-- codegen:end -->', } as const -export const MARKER = { +export let MARKER = { Start: `\`\`\`mermaid`, End: `\`\`\``, } as const -export const PATTERN = { +export let PATTERN = { ChartReplacement: (chart: string) => `${MARKER.Start}\n${chart}\n${MARKER.End}`, DependencyGraph: `${MARKER.Start}([^]*?)${MARKER.End}`, FlattenOnce: { open: `(.*)../`, close: `(.*)` }, @@ -22,7 +22,7 @@ export const PATTERN = { PackageList: `${PKG_LIST.Start}([^]*?)${PKG_LIST.End}`, } as const -export const REG_EXP = { +export let REG_EXP = { DependencyGraph: new globalThis.RegExp(PATTERN.DependencyGraph, 'g'), FlattenOnce: (dirPath: string) => new RegExp(`${PATTERN.FlattenOnce.open}${dirPath}${PATTERN.FlattenOnce.close}`, 'gm'), @@ -33,7 +33,7 @@ export const REG_EXP = { WordBoundary: /([-_][a-z])/gi, } as const -export const PATH = { +export let PATH = { readme: path.join(path.resolve(), 'README.md'), schemaReadme: path.join(path.resolve(), 'packages', 'schema', 'README.md'), generated: path.join(path.resolve(), 'config', '__generated__'), @@ -41,7 +41,7 @@ export const PATH = { generated_package_list: path.join(path.resolve(), 'config', '__generated__', 'package-list.ts'), } as const -export const RELATIVE_PATH = { +export let RELATIVE_PATH = { dist: 'dist', build: 'build', src: 'src', @@ -73,13 +73,13 @@ export const RELATIVE_PATH = { ], } as const -export const REPO +export let REPO : Repo = globalThis.JSON.parse(fs.readFileSync(PATH.generated_repo_metadata).toString('utf8')) export const SCOPE: '@traversable' = REPO.scope as never -export const defaults = { +export let defaults = { config: { generateExports: { include: ['*.ts'], @@ -96,25 +96,25 @@ export const defaults = { }, } as const -export const GLOB = { +export let GLOB = { all_packages: 'packages/*/', all_packages_src: 'packages/*/src/**/*.ts', } as const -export const PACKAGES: string[] = glob.sync(GLOB.all_packages) -export const GRAPH = ['.', ...PACKAGES] as const +export let PACKAGES: string[] = glob.sync(GLOB.all_packages) +export let GRAPH = ['.', ...PACKAGES] as const -export const BUILD_ARTIFACTS = [ +export let BUILD_ARTIFACTS = [ '.tsbuildinfo', 'dist', 'build', ] as const -export const BUILD_DEPS = [ +export let BUILD_DEPS = [ 'node_modules', ] as const -export const EMOJI = { +export let EMOJI = { ERR: '٩◔̯◔۶', HEY: 'ʕ•̫͡•ʔ', OOO: '°ﺑ°', @@ -136,3 +136,38 @@ export const EMOJI = { ADMIT_ONE: '🎟', FLAG: '🚩', } as const + +export let ALPHABET_MAP = { + a: '𝗮', + b: '𝗯', + c: '𝗰', + d: '𝗱', + e: '𝗲', + f: '𝗳', + g: '𝗴', + h: '𝗵', + i: '𝗶', + j: '𝗷', + k: '𝗸', + l: '𝗹', + m: '𝗺', + n: '𝗻', + o: '𝗼', + p: '𝗽', + q: '𝗾', + r: '𝗿', + s: '𝘀', + t: '𝘁', + u: '𝘂', + v: '𝘃', + w: '𝘄', + x: '𝘅', + y: '𝘆', + z: '𝘇', +} as const satisfies Record + +export let TEMPLATE = { + Start: '<%= ', + End: ' =>', + new: (varName: T) => `<%= ${varName} =>` as const, +} as const diff --git a/bin/workspace-create.ts b/bin/workspace-create.ts index 174ecbd8..e7f53891 100755 --- a/bin/workspace-create.ts +++ b/bin/workspace-create.ts @@ -22,25 +22,24 @@ const env = Prompt.select({ const visibility = Prompt.select({ message: `Initialize the package as private (will not auto-publish)?`, choices: [ - { title: "true", value: true }, { title: "false", value: false }, + { title: "true", value: true }, ] as const }) const localDeps = Prompt.list({ - message: `Which will your workspace depend on?\n\ncomma separated list containing any of: \n\n ${ - [...PACKAGES].sort().map(pkg => pkg.slice("packages/".length)).join(", ") - }\n` , + message: `Which will your workspace depend on?\n\ncomma separated list containing any of: \n\n ${[...PACKAGES].sort().map(pkg => pkg.slice("packages/".length)).join(", ") + }\n`, delimiter: ", " }) const command = Command.prompt( - "New workspace", + "New workspace", Prompt.all([pkgName, env, localDeps, visibility]), - ([ pkgName, env, localDeps, private_ ]) => + ([pkgName, env, localDeps, private_]) => Effect.sync(() => main({ pkgName, env, localDeps, private: private_, dryRun: false }) -)) + )) const cli = Command.run(command, { name: "Generate an empty package", diff --git a/bin/workspace.ts b/bin/workspace.ts index a8299aa4..e4ad9916 100755 --- a/bin/workspace.ts +++ b/bin/workspace.ts @@ -3,14 +3,15 @@ import * as path from 'node:path' import { identity, pipe, Effect } from 'effect' import * as fs from './fs.js' -import { template } from './assets/index.js' +import { template, getReadmeTemplate } from './assets/index.js' import { Print, tap, Transform } from './util.js' import * as S from 'effect/Schema' -import { SCOPE } from 'bin/constants.js' +import { ALPHABET_MAP, TEMPLATE as Template, SCOPE } from 'bin/constants.js' const $$ = (command: string) => process.execSync(command, { stdio: 'inherit' }) let ix = 0 + const PATH = { packages: path.join(path.resolve(), 'packages'), vitestSharedConfig: path.join(path.resolve(), 'vite.config.ts'), @@ -19,6 +20,14 @@ const PATH = { rootTsConfigBuild: path.join(path.resolve(), 'tsconfig.build.json'), } as const +const PATTERN = { + PkgName: `${Template.Start}([^]*?)${Template.End}`, +} as const + +const REG_EXP = { + PkgName: new RegExp(PATTERN.PkgName, 'g'), +} as const + const TEMPLATE = { RootKey: 'packages/', BuildKey: { pre: 'packages/', post: '/tsconfig.build.json' }, @@ -26,6 +35,7 @@ const TEMPLATE = { BaseKey$: { pre: `${SCOPE}/`, post: '/*' }, BaseValue: { pre: 'packages/', post: '/src/index.js' }, BaseValue$: { pre: 'packages/', post: '/*.js' }, + PkgName: Template.new('pkgName'), } as const interface Deps { @@ -459,12 +469,34 @@ namespace write { : fs.rimraf(path.join(PATH.packages, $.pkgName, 'vite.config.ts')), ) - export const workspaceReadme = defineEffect( + /** + * @example + * export let PATTERN = { + * ChartReplacement: (chart: string) => `${MARKER.Start}\n${chart}\n${MARKER.End}`, + * DependencyGraph: `${MARKER.Start}([^]*?)${MARKER.End}`, + * FlattenOnce: { open: `(.*)../`, close: `(.*)` }, + * ListReplacement: (list: string) => `${PKG_LIST.Start}\n${list}\n${PKG_LIST.End}`, + * NonWhitespace: '\\w', + * PackageList: `${PKG_LIST.Start}([^]*?)${PKG_LIST.End}`, + * } as const + * + * export let REG_EXP = { + * DependencyGraph: new globalThis.RegExp(PATTERN.DependencyGraph, 'g'), + * FlattenOnce: (dirPath: string) => + * new RegExp(`${PATTERN.FlattenOnce.open}${dirPath}${PATTERN.FlattenOnce.close}`, 'gm'), + * NonWhitespace: new globalThis.RegExp(PATTERN.NonWhitespace, 'g'), + * PackageList: new globalThis.RegExp(PATTERN.PackageList, 'g'), + * Semver: /(\d)+\.(\d)+\.(\d)+/g, + * Target: /<>/, + * WordBoundary: /([-_][a-z])/gi, + * } as const + */ + + export let workspaceReadme = defineEffect( ($) => pipe( - [ - `# ${SCOPE}/${$.pkgName}` - ].join('\n'), - $.dryRun ? tap(`\n\n[CREATE #13]: workspaceReadme\n`, globalThis.String) + getReadmeTemplate(), + (readme) => readme.replace(REG_EXP.PkgName, $.pkgName), + $.dryRun ? tap(`\n\n[CREATE #13]: readmeTemplate\n`, globalThis.String) : fs.writeString(path.join(PATH.packages, $.pkgName, 'README.md')), ), ($) => @@ -473,6 +505,20 @@ namespace write { : fs.rimraf(path.join(PATH.packages, $.pkgName, 'README.md')), ) + // export const workspaceReadme = defineEffect( + // ($) => pipe( + // [ + // `# ${SCOPE}/${$.pkgName}` + // ].join('\n'), + // $.dryRun ? tap(`\n\n[CREATE #14]: workspaceReadme\n`, globalThis.String) + // : fs.writeString(path.join(PATH.packages, $.pkgName, 'README.md')), + // ), + // ($) => + // $.dryRun + // ? tap(`\n\n[CLEANUP #14]: workspaceReadme\n`, globalThis.String) + // : fs.rimraf(path.join(PATH.packages, $.pkgName, 'README.md')), + // ) + export const workspaceSrcVersion = defineEffect( ($) => pipe( [ diff --git a/vite.config.ts b/vite.config.ts index 90858cfb..87eccd10 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -30,7 +30,7 @@ export default defineConfig({ enabled: true, reporter: ['html'], reportsDirectory: './config/coverage', - thresholds: { "100": true }, + thresholds: { 100: true }, }, disableConsoleIntercept: true, fakeTimers: { toFake: undefined }, @@ -52,4 +52,3 @@ export default defineConfig({ ], }, }) - From bcf4b10a3abef855c24b6397b5394429ea8a07a3 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 17:32:06 -0500 Subject: [PATCH 03/45] fix(build): uses node:path to import README template --- bin/assets/index.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/bin/assets/index.ts b/bin/assets/index.ts index ac5d30eb..53243968 100644 --- a/bin/assets/index.ts +++ b/bin/assets/index.ts @@ -1,5 +1,10 @@ import * as fs from 'node:fs' +import * as path from 'node:path' + +const PATH = { + README: path.join(path.resolve(), 'bin', 'assets', '_README.md') +} as const export { default as template } from "./_package.json" -export let getReadmeTemplate = () => fs.readFileSync('./_README.md').toString('utf8') +export let getReadmeTemplate = () => fs.readFileSync(PATH.README).toString('utf8') From dfd660ef24e282f8a9f5e9dfe604361db7dabb9e Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 17:39:21 -0500 Subject: [PATCH 04/45] feat(build): apply package header when generating readme --- bin/workspace.ts | 29 +++++------------------------ 1 file changed, 5 insertions(+), 24 deletions(-) diff --git a/bin/workspace.ts b/bin/workspace.ts index e4ad9916..c0962076 100755 --- a/bin/workspace.ts +++ b/bin/workspace.ts @@ -21,11 +21,13 @@ const PATH = { } as const const PATTERN = { - PkgName: `${Template.Start}([^]*?)${Template.End}`, + PkgName: Template.new('pkgName'), // `${Template.Start}([^]*?)${Template.End}`, + PkgHeader: Template.new('pkgHeader'), } as const const REG_EXP = { PkgName: new RegExp(PATTERN.PkgName, 'g'), + PkgHeader: new RegExp(PATTERN.PkgHeader, 'g'), } as const const TEMPLATE = { @@ -35,7 +37,6 @@ const TEMPLATE = { BaseKey$: { pre: `${SCOPE}/`, post: '/*' }, BaseValue: { pre: 'packages/', post: '/src/index.js' }, BaseValue$: { pre: 'packages/', post: '/*.js' }, - PkgName: Template.new('pkgName'), } as const interface Deps { @@ -469,32 +470,12 @@ namespace write { : fs.rimraf(path.join(PATH.packages, $.pkgName, 'vite.config.ts')), ) - /** - * @example - * export let PATTERN = { - * ChartReplacement: (chart: string) => `${MARKER.Start}\n${chart}\n${MARKER.End}`, - * DependencyGraph: `${MARKER.Start}([^]*?)${MARKER.End}`, - * FlattenOnce: { open: `(.*)../`, close: `(.*)` }, - * ListReplacement: (list: string) => `${PKG_LIST.Start}\n${list}\n${PKG_LIST.End}`, - * NonWhitespace: '\\w', - * PackageList: `${PKG_LIST.Start}([^]*?)${PKG_LIST.End}`, - * } as const - * - * export let REG_EXP = { - * DependencyGraph: new globalThis.RegExp(PATTERN.DependencyGraph, 'g'), - * FlattenOnce: (dirPath: string) => - * new RegExp(`${PATTERN.FlattenOnce.open}${dirPath}${PATTERN.FlattenOnce.close}`, 'gm'), - * NonWhitespace: new globalThis.RegExp(PATTERN.NonWhitespace, 'g'), - * PackageList: new globalThis.RegExp(PATTERN.PackageList, 'g'), - * Semver: /(\d)+\.(\d)+\.(\d)+/g, - * Target: /<>/, - * WordBoundary: /([-_][a-z])/gi, - * } as const - */ + let toPackageHeader = (pkgName: string) => [...pkgName].map((char) => char in ALPHABET_MAP ? ALPHABET_MAP[char as keyof typeof ALPHABET_MAP] : char).join('') export let workspaceReadme = defineEffect( ($) => pipe( getReadmeTemplate(), + (readme) => readme.replace(REG_EXP.PkgHeader, toPackageHeader($.pkgName)), (readme) => readme.replace(REG_EXP.PkgName, $.pkgName), $.dryRun ? tap(`\n\n[CREATE #13]: readmeTemplate\n`, globalThis.String) : fs.writeString(path.join(PATH.packages, $.pkgName, 'README.md')), From f34799828e919ca1ebb9f3991b70be45f617232b Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 17:45:12 -0500 Subject: [PATCH 05/45] fix(build): updates README template to use `pkgHeader` variable --- bin/assets/_README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/assets/_README.md b/bin/assets/_README.md index 5c826c74..d620d429 100644 --- a/bin/assets/_README.md +++ b/bin/assets/_README.md @@ -1,5 +1,5 @@
-

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/<%= pkgName =>

+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/<%= pkgHeader =>


From 1843df3cefcac46f4bbcc26cb17d37524c673205 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 17:48:57 -0500 Subject: [PATCH 06/45] fix(root): schema coverage --- vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vite.config.ts b/vite.config.ts index 87eccd10..9bdd87a1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -30,7 +30,7 @@ export default defineConfig({ enabled: true, reporter: ['html'], reportsDirectory: './config/coverage', - thresholds: { 100: true }, + thresholds: { "100": true }, }, disableConsoleIntercept: true, fakeTimers: { toFake: undefined }, From 85d3563cec0967c7e7e55abecffe54f0a840d2b6 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 17:51:43 -0500 Subject: [PATCH 07/45] init(generator): initialize package --- README.md | 2 + config/__generated__/package-list.ts | 1 + packages/schema-generator/README.md | 38 +++++++++++++++ packages/schema-generator/package.json | 46 +++++++++++++++++++ .../src/__generated__/__manifest__.ts | 46 +++++++++++++++++++ packages/schema-generator/src/exports.ts | 1 + packages/schema-generator/src/index.ts | 2 + packages/schema-generator/src/version.ts | 3 ++ .../schema-generator/test/version.test.ts | 10 ++++ packages/schema-generator/tsconfig.build.json | 11 +++++ packages/schema-generator/tsconfig.json | 8 ++++ packages/schema-generator/tsconfig.src.json | 11 +++++ packages/schema-generator/tsconfig.test.json | 15 ++++++ packages/schema-generator/vite.config.ts | 6 +++ pnpm-lock.yaml | 10 ++++ tsconfig.base.json | 40 ++++++++++++---- tsconfig.build.json | 1 + tsconfig.json | 1 + 18 files changed, 242 insertions(+), 10 deletions(-) create mode 100644 packages/schema-generator/README.md create mode 100644 packages/schema-generator/package.json create mode 100644 packages/schema-generator/src/__generated__/__manifest__.ts create mode 100644 packages/schema-generator/src/exports.ts create mode 100644 packages/schema-generator/src/index.ts create mode 100644 packages/schema-generator/src/version.ts create mode 100644 packages/schema-generator/test/version.test.ts create mode 100644 packages/schema-generator/tsconfig.build.json create mode 100644 packages/schema-generator/tsconfig.json create mode 100644 packages/schema-generator/tsconfig.src.json create mode 100644 packages/schema-generator/tsconfig.test.json create mode 100644 packages/schema-generator/vite.config.ts diff --git a/README.md b/README.md index 649bae65..cbeffba3 100644 --- a/README.md +++ b/README.md @@ -393,6 +393,8 @@ flowchart TD derive-validators(derive-validators) -.-> json(json) derive-validators(derive-validators) -.-> registry(registry) derive-validators(derive-validators) -.-> schema(schema) + schema-generator(schema-generator) -.-> registry(registry) + schema-generator(schema-generator) -.-> schema(schema) schema-seed(schema-seed) -.-> json(json) schema-seed(schema-seed) -.-> registry(registry) schema-seed(schema-seed) -.-> schema(schema) diff --git a/config/__generated__/package-list.ts b/config/__generated__/package-list.ts index b8600883..3ec1989a 100644 --- a/config/__generated__/package-list.ts +++ b/config/__generated__/package-list.ts @@ -5,6 +5,7 @@ export const PACKAGES = [ "packages/json", "packages/registry", "packages/schema", + "packages/schema-generator", "packages/schema-seed", "packages/schema-to-json-schema", "packages/schema-to-string", diff --git a/packages/schema-generator/README.md b/packages/schema-generator/README.md new file mode 100644 index 00000000..62d25f35 --- /dev/null +++ b/packages/schema-generator/README.md @@ -0,0 +1,38 @@ +
+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘀𝗰𝗵𝗲𝗺𝗮-𝗴𝗲𝗻𝗲𝗿𝗮𝘁𝗼𝗿

+
+ +

+ Internal package that generates schemas. Keeps schemas fast and bundles small by making the `@traversable/schema` feature set 100% opt-in. +

+ +
+ NPM Version +   + TypeScript +   + Static Badge +   + npm +   +
+ +
+ npm bundle size (scoped) +   + Static Badge +   + Static Badge +   +
+ +
+ Demo (StackBlitz) +   •   + TypeScript Playground +   •   + npm +
+
+
+
diff --git a/packages/schema-generator/package.json b/packages/schema-generator/package.json new file mode 100644 index 00000000..4d380807 --- /dev/null +++ b/packages/schema-generator/package.json @@ -0,0 +1,46 @@ +{ + "name": "@traversable/schema-generator", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/schema-generator" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema": "workspace:^" + }, + "devDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema": "workspace:^" + } +} diff --git a/packages/schema-generator/src/__generated__/__manifest__.ts b/packages/schema-generator/src/__generated__/__manifest__.ts new file mode 100644 index 00000000..eb0e557c --- /dev/null +++ b/packages/schema-generator/src/__generated__/__manifest__.ts @@ -0,0 +1,46 @@ +export default { + "name": "@traversable/schema-generator", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/schema-generator" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema": "workspace:^" + }, + "devDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema": "workspace:^" + } +} as const \ No newline at end of file diff --git a/packages/schema-generator/src/exports.ts b/packages/schema-generator/src/exports.ts new file mode 100644 index 00000000..04783bce --- /dev/null +++ b/packages/schema-generator/src/exports.ts @@ -0,0 +1 @@ +export * from './version.js' \ No newline at end of file diff --git a/packages/schema-generator/src/index.ts b/packages/schema-generator/src/index.ts new file mode 100644 index 00000000..2e75b3f3 --- /dev/null +++ b/packages/schema-generator/src/index.ts @@ -0,0 +1,2 @@ +export * from './exports.js' +export * as schemaGenerator from './exports.js' \ No newline at end of file diff --git a/packages/schema-generator/src/version.ts b/packages/schema-generator/src/version.ts new file mode 100644 index 00000000..388bbc3e --- /dev/null +++ b/packages/schema-generator/src/version.ts @@ -0,0 +1,3 @@ +import pkg from './__generated__/__manifest__.js' +export const VERSION = `${pkg.name}@${pkg.version}` as const +export type VERSION = typeof VERSION \ No newline at end of file diff --git a/packages/schema-generator/test/version.test.ts b/packages/schema-generator/test/version.test.ts new file mode 100644 index 00000000..23f422a9 --- /dev/null +++ b/packages/schema-generator/test/version.test.ts @@ -0,0 +1,10 @@ +import * as vi from 'vitest' +import pkg from '../package.json' with { type: 'json' } +import { schemaGenerator } from '@traversable/schema-generator' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { + vi.it('〖⛳️〗› ❲schemaGenerator#VERSION❳', () => { + const expected = `${pkg.name}@${pkg.version}` + vi.assert.equal(schemaGenerator.VERSION, expected) + }) +}) \ No newline at end of file diff --git a/packages/schema-generator/tsconfig.build.json b/packages/schema-generator/tsconfig.build.json new file mode 100644 index 00000000..3a9eb4e2 --- /dev/null +++ b/packages/schema-generator/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.src.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", + "types": ["node"], + "declarationDir": "build/dts", + "outDir": "build/esm", + "stripInternal": true + }, + "references": [{ "path": "../registry" }, { "path": "../schema" }] +} diff --git a/packages/schema-generator/tsconfig.json b/packages/schema-generator/tsconfig.json new file mode 100644 index 00000000..2c291d21 --- /dev/null +++ b/packages/schema-generator/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [], + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "tsconfig.test.json" } + ] +} diff --git a/packages/schema-generator/tsconfig.src.json b/packages/schema-generator/tsconfig.src.json new file mode 100644 index 00000000..446386e4 --- /dev/null +++ b/packages/schema-generator/tsconfig.src.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", + "rootDir": "src", + "types": ["node"], + "outDir": "build/src" + }, + "references": [{ "path": "../registry" }, { "path": "../schema" }], + "include": ["src"] +} diff --git a/packages/schema-generator/tsconfig.test.json b/packages/schema-generator/tsconfig.test.json new file mode 100644 index 00000000..fd50be21 --- /dev/null +++ b/packages/schema-generator/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", + "rootDir": "test", + "types": ["node"], + "noEmit": true + }, + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "../registry" }, + { "path": "../schema" } + ], + "include": ["test"] +} diff --git a/packages/schema-generator/vite.config.ts b/packages/schema-generator/vite.config.ts new file mode 100644 index 00000000..64dba4ad --- /dev/null +++ b/packages/schema-generator/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import sharedConfig from '../../vite.config.js' + +const localConfig = defineConfig({}) + +export default mergeConfig(sharedConfig, localConfig) \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02a62bb1..6b4dd879 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -258,6 +258,16 @@ importers: version: 3.24.2 publishDirectory: dist + packages/schema-generator: + devDependencies: + '@traversable/registry': + specifier: workspace:^ + version: link:../registry/dist + '@traversable/schema': + specifier: workspace:^ + version: link:../schema/dist + publishDirectory: dist + packages/schema-seed: devDependencies: '@traversable/json': diff --git a/tsconfig.base.json b/tsconfig.base.json index 22fb6db7..bc6edfe5 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -32,24 +32,44 @@ "@traversable/derive-codec/*": ["packages/derive-codec/src/*.js"], "@traversable/derive-equals": ["packages/derive-equals/src/index.js"], "@traversable/derive-equals/*": ["packages/derive-equals/src/*.js"], - "@traversable/derive-validators": ["packages/derive-validators/src/index.js"], - "@traversable/derive-validators/*": ["packages/derive-validators/src/*.js"], + "@traversable/derive-validators": [ + "packages/derive-validators/src/index.js" + ], + "@traversable/derive-validators/*": [ + "packages/derive-validators/src/*.js" + ], "@traversable/json": ["packages/json/src/index.js"], "@traversable/json/*": ["packages/json/src/*.js"], "@traversable/registry": ["packages/registry/src/index.js"], "@traversable/registry/*": ["packages/registry/src/*.js"], "@traversable/schema": ["packages/schema/src/index.js"], - "@traversable/schema/*": ["packages/schema/src/*.js"], + "@traversable/schema-generator": [ + "packages/schema-generator/src/index.js" + ], + "@traversable/schema-generator/*": ["packages/schema-generator/*.js"], "@traversable/schema-seed": ["packages/schema-seed/src/index.js"], "@traversable/schema-seed/*": ["packages/schema-seed/src/*.js"], - "@traversable/schema-to-json-schema": ["packages/schema-to-json-schema/src/index.js"], - "@traversable/schema-to-json-schema/*": ["packages/schema-to-json-schema/src/*.js"], - "@traversable/schema-to-string": ["packages/schema-to-string/src/index.js"], + "@traversable/schema-to-json-schema": [ + "packages/schema-to-json-schema/src/index.js" + ], + "@traversable/schema-to-json-schema/*": [ + "packages/schema-to-json-schema/src/*.js" + ], + "@traversable/schema-to-string": [ + "packages/schema-to-string/src/index.js" + ], "@traversable/schema-to-string/*": ["packages/schema-to-string/src/*.js"], - "@traversable/schema-valibot-adapter": ["packages/schema-valibot-adapter/src/index.js"], - "@traversable/schema-valibot-adapter/*": [ "packages/schema-valibot-adapter/src/*.js" ], - "@traversable/schema-zod-adapter": ["packages/schema-zod-adapter/src/index.js"], - "@traversable/schema-zod-adapter/*": ["packages/schema-zod-adapter/*.js"] + "@traversable/schema-valibot-adapter": [ + "packages/schema-valibot-adapter/src/index.js" + ], + "@traversable/schema-valibot-adapter/*": [ + "packages/schema-valibot-adapter/src/*.js" + ], + "@traversable/schema-zod-adapter": [ + "packages/schema-zod-adapter/src/index.js" + ], + "@traversable/schema-zod-adapter/*": ["packages/schema-zod-adapter/*.js"], + "@traversable/schema/*": ["packages/schema/src/*.js"] } } } diff --git a/tsconfig.build.json b/tsconfig.build.json index 808c4b4d..68c7ae96 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -7,6 +7,7 @@ { "path": "packages/derive-validators/tsconfig.build.json" }, { "path": "packages/json/tsconfig.build.json" }, { "path": "packages/registry/tsconfig.build.json" }, + { "path": "packages/schema-generator/tsconfig.build.json" }, { "path": "packages/schema-seed/tsconfig.build.json" }, { "path": "packages/schema-to-json-schema/tsconfig.build.json" }, { "path": "packages/schema-to-string/tsconfig.build.json" }, diff --git a/tsconfig.json b/tsconfig.json index 376603e0..7289c45c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ { "path": "packages/json" }, { "path": "packages/registry" }, { "path": "packages/schema" }, + { "path": "packages/schema-generator" }, { "path": "packages/schema-seed" }, { "path": "packages/schema-to-json-schema" }, { "path": "packages/schema-to-string" }, From 72217acae7f27c4846f43c7b355bce7caeab2298 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 18:19:35 -0500 Subject: [PATCH 08/45] feat(generator): adds tiny parser combinator library --- packages/schema-generator/src/exports.ts | 3 +- packages/schema-generator/src/index.ts | 1 - .../src/parser-combinators.ts | 336 ++++++++++++++++++ .../test/parser-combinators.test.ts | 214 +++++++++++ .../schema-generator/test/version.test.ts | 4 +- 5 files changed, 554 insertions(+), 4 deletions(-) create mode 100644 packages/schema-generator/src/parser-combinators.ts create mode 100644 packages/schema-generator/test/parser-combinators.test.ts diff --git a/packages/schema-generator/src/exports.ts b/packages/schema-generator/src/exports.ts index 04783bce..a5658091 100644 --- a/packages/schema-generator/src/exports.ts +++ b/packages/schema-generator/src/exports.ts @@ -1 +1,2 @@ -export * from './version.js' \ No newline at end of file +export * from './version.js' +export * as P from './parser-combinators.js' diff --git a/packages/schema-generator/src/index.ts b/packages/schema-generator/src/index.ts index 2e75b3f3..410a4bcb 100644 --- a/packages/schema-generator/src/index.ts +++ b/packages/schema-generator/src/index.ts @@ -1,2 +1 @@ export * from './exports.js' -export * as schemaGenerator from './exports.js' \ No newline at end of file diff --git a/packages/schema-generator/src/parser-combinators.ts b/packages/schema-generator/src/parser-combinators.ts new file mode 100644 index 00000000..aede890f --- /dev/null +++ b/packages/schema-generator/src/parser-combinators.ts @@ -0,0 +1,336 @@ +export interface Success { + success: true + index: number + value: T +} + +export interface Failure { + success: false + index: number +} + +export type Result = Success | Failure + +export type ResultTypes = U extends [infer Head, ...infer Tail] ? [Parser.typeof, ...ResultTypes] : []; + +export type ParserHandler = (input: string, index: number, state: any) => Result + +export type Options = { + handler: ParserHandler + name?: string + info?: string +} + +export interface ParserContext { + handler(input: string, index: number, state: any): Result +} + +let PATTERN = { + Alpha: /[a-z]/i, + Digit: /[0-9]/, + Whitespace: /\s+/, + Alphanum: /[a-zA-Z0-9]/, + Id: /^[_$a-zA-Z][_$a-zA-Z0-9]*/, +} satisfies { [x: string]: RegExp } + +export function success(index: number, value: T): Success { + return { + success: true, + value: value, + index: index, + } +} + +export function failure(index: number): Failure { + return { + success: false, + index: index + } +} + +export function succeed(value: U): Parser { + return Parser.new((_, index) => { + return success(index, value); + }, 'succeeded'); +} + +function withTrace(handler: ParserHandler, tag: string): ParserHandler { + return (input, index, state) => { + if (state.trace) { + const pos = `${index}` + console.log(`${pos.padEnd(6, ' ')}enter ${tag}`) + const result = handler(input, index, state) + if (result.success) { + const pos = `${index}:${result.index}` + console.log(`${pos.padEnd(6, ' ')}success ${tag}`) + } else { + const pos = `${index}` + console.log(`${pos.padEnd(6, ' ')}failure ${tag}`) + } + return result + } + return handler(input, index, state) + } +} + +export class Parser { + static new + : ( + handler: ParserHandler, + tag?: string, + info?: string, + ) => Parser + = (handler, tag, info) => new Parser(handler, tag ?? '', info) + + many(): Parser + many(options: many.Options): Parser + many($: many.Options = {}): Parser { return many(this, $) } + + map(f: (value: S) => T): Parser { return map(this, f) } + + parse(input: string, state?: State): Result + parse(input: string, state = {}): Result { return parse(this, input, state) } + + run(input: string): Result + run(input: string, state?: State, offset?: number, debug?: boolean): Result + run(input: string, state = {}, offset: number = 0, debug = false): Result { + let handler = debug ? withTrace(this.handler, this.tag) : this.handler + return handler(input, offset, state) + } + + times(n: 1): Parser<[S]> + times(n: 2): Parser<[S, S]> + times(n: 3): Parser<[S, S, S]> + times(n: 4): Parser<[S, S, S, S]> + times(n: 5): Parser<[S, S, S, S, S]> + times(n: 6): Parser<[S, S, S, S, S, S]> + times(n: 7): Parser<[S, S, S, S, S, S, S]> + times(n: 8): Parser<[S, S, S, S, S, S, S, S]> + times(n: 9): Parser<[S, S, S, S, S, S, S, S, S]> + times(n: 0): Parser<[]> + times(n: number): Parser + times(n: number): Parser { return times(this, n) } + + trim(): Parser { return index([spaces, this, spaces], 1) } + + private constructor( + private handler: ParserHandler, + public tag: string, + public info?: string, + ) { } +} + +export declare namespace Parser { + export { typeOf as typeof } + export type typeOf

= P extends Parser ? T : never +} + +function map(parser: Parser, f: (s: S) => T): Parser { + return Parser.new((input, index, state) => { + const result = parser.run(input, state, index) + if (!result.success) { + return result + } + return success(result.index, f(result.value)) + }, 'map', parser.tag) +} + +function parse(parser: Parser, input: string, state: State): Result +function parse(p: Parser, input: string, state: State): Result { + let parser = index([p, eof], 0) + return parser.run(input, state, 0) +} + +function times(parser: Parser, n: number): Parser { + if (n < 1 || !Number.isInteger(n)) + throw Error('Expected "n" to be a non-negative, non-zero integer') + return Parser.new((input, ix, state) => { + let result + let cursor = ix + const seed = Array.of() + while (cursor < input.length) { + result = parser.run(input, state, cursor) + if (!result.success) break + cursor = result.index + seed.push(result.value) + } + if (seed.length < n) return failure(cursor) + else return success(cursor, seed) + }) +} + + +export function seq(...parsers: { [I in keyof T]: Parser }): Parser +export function seq(...parsers: Parser[]): Parser { + return Parser.new((input, index, state) => { + let result + let latestIndex = index + let seed = Array.of() + for (let i = 0; i < parsers.length; i++) { + result = parsers[i].run(input, state, latestIndex) + if (!result.success) { + return result + } + latestIndex = result.index + seed.push(result.value) + } + return success(latestIndex, seed) + }, 'seq', `length=${parsers.length}`) +} + +export function alt(...parsers: { [I in keyof T]: Parser }): Parser { + return Parser.new((input, index, state) => { + let result + for (let ix = 0, len = parsers.length; ix < len; ix++) { + result = parsers[ix].run(input, state, index) + if (result.success) return result + } + return failure(index) + }, `alt length=${parsers.length}`) +} + +export function string(value: T): Parser { + return Parser.new((input, index) => { + if ((input.length - index) < value.length) + return failure(index) + else if (input.slice(index, index + value.length) !== value) + return failure(index) + else return success(index + value.length, value) + }, `string=${value}`) +} + +export function index< + T extends readonly unknown[], + I extends keyof T & number +>( + parsers: { [I in keyof T]: Parser }, + index: I +): Parser { + return seq(...parsers).map(values => values[index]); +} + +export function regexp(pattern: RegExp): Parser { + const expr = RegExp(`^(?:${pattern.source})`, pattern.flags); + return Parser.new((input, index) => { + const text = input.slice(index); + const result = expr.exec(text); + if (result == null) { + return failure(index); + } + return success(index + result[0].length, result[0]); + }, `pattern=${pattern}`); +} + +export type Char = [T] extends [`${string}${infer T}`] ? T extends '' ? string : never : never +export function char(): Parser +export function char>(char: T): Parser +export function char(char: string = '') { + return char === '' ? anyChar : string(char) +} + +export const anyChar = Parser.new((input, index) => { + if ((input.length - index) < 1) + return failure(index) + const value = input[index] + return success(index + 1, value) +}, 'any') + + +export function not(parser: Parser): Parser { + return Parser.new((input, index, state) => { + const result = parser.run(input, state, index) + return !result.success + ? success(index, null) + : failure(index) + }, 'not') +} + +export function many(parser: Parser, options?: many.Options): Parser +export function many(parser: Parser, $: many.Options = {}): Parser { + if (!!$.not) return many( + index([not($.not), parser], 1), + { min: $.min, max: $.max } + ) + else return Parser.new((input, ix, state) => { + let result + let cursor = ix + const seed = Array.of() + while (cursor < input.length) { + result = parser.run(input, state, cursor) + if (!result.success) break + cursor = result.index + seed.push(result.value) + } + if ($.min != null && seed.length < $.min) + return failure(cursor) + else if ($.max != null && seed.length > $.max) + return failure(cursor) + else return success(cursor, seed) + }, 'many') +} + +export declare namespace many { + type Options = { + min?: number + max?: number + not?: Parser + } +} + +export let trim = (parser: Parser) => index([ + spaces, + parser, + spaces, +], 1) + +export let eof + : Parser + = Parser.new( + (input, index) => index >= input.length ? success(index, null) : failure(index), + 'eof' + ) + +export let optional + : (parser: Parser) => Parser + = (parser) => alt(parser, succeed(null)) + +export let CR = char('\r') +export let LF = char('\n') +export let CRLF = string('\r\n') +export let newline = alt(CRLF, CR, LF) +export let whitespace = regexp(PATTERN.Whitespace) +export let spaces = optional(regexp(/[ \t\r\n]/).many()) + +export let alpha = regexp(PATTERN.Alpha) +export let digit = regexp(PATTERN.Digit) +export let alphanum = regexp(PATTERN.Alphanum) +export let ident = regexp(PATTERN.Id) + +export function lazy(thunk: () => Parser, tag?: string): Parser { + return Parser.new((input, index, state) => { + let parser = thunk() + return parser.run(input, state, index) + }) +} + +export type Language = { [K in keyof U]: U[K] extends Parser ? U[K] : never }; + +export type LanguageSource */> = { [K in keyof U]: (lang: U) => U[K] }; + +export function language>(source: LanguageSource): T +export function language(source: { [x: string]: (go: Record>) => Parser }) { + let lang: Record> = {} + for (const k of Object.keys(source)) { + let loop = source[k] + lang[k] = lazy(() => { + const parser = loop(lang) + console.log('parser', parser) + if (parser == null || !(parser instanceof Parser)) { + throw new Error('syntax must return a Parser.') + } + parser.tag = `${parser.tag} key=${k}` + return parser + }) + } + return lang +} diff --git a/packages/schema-generator/test/parser-combinators.test.ts b/packages/schema-generator/test/parser-combinators.test.ts new file mode 100644 index 00000000..1ef60721 --- /dev/null +++ b/packages/schema-generator/test/parser-combinators.test.ts @@ -0,0 +1,214 @@ +import * as vi from "vitest" + +import { P } from '@traversable/schema-generator' + +/** @internal */ +let Object_values + : (xs: T) => T[keyof T][] + = globalThis.Object.values + +export let key = P.seq( + P.optional(P.char('"')), + P.ident, + P.optional(P.char('"')), +).map(([, k]) => k) + +export let propertyValue = P.char().many({ not: P.alt(P.char(','), P.char('}')) }).map((xs) => xs.join('')) + +export let entry = P.seq( + key, + P.optional(P.whitespace), + P.char(':'), + P.optional(P.whitespace), + propertyValue, +).map(([key, , , , value]) => [key, value] as [k: string, v: string]) + +export let comma = P.seq( + P.spaces, + P.char(','), + P.spaces, +).map((_) => _[1]) + +export let entriesDanglingComma = P.seq( + P.seq(P.trim(entry), P.char(',')).map(([_]) => _).many(), + P.optional(P.trim(entry)), +).map(([xs, x]) => x === null ? xs : [...xs, x]) + +export let parseObjectEntries = P.seq( + P.char('{'), + P.trim(entriesDanglingComma), + P.char('}'), +).map(([, _]) => _ === null ? [] : _) + +vi.describe("〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳", () => { + vi.it("〖️⛳️〗› ❲P❳", () => { + + vi.expect(P.string('hey jude').run('hey jude')).toMatchInlineSnapshot(` + { + "index": 8, + "success": true, + "value": "hey jude", + } + `) + vi.expect(P.regexp(/type/g).run('type')).toMatchInlineSnapshot(` + { + "index": 4, + "success": true, + "value": "type", + } + `) + + vi.expect(P.ident.run('abc')).toMatchInlineSnapshot(` + { + "index": 3, + "success": true, + "value": "abc", + } + `) + + vi.expect(P.ident.run('1_ab')).toMatchInlineSnapshot(` + { + "index": 0, + "success": false, + } + `) + + vi.expect(P.whitespace.run('')).toMatchInlineSnapshot(` + { + "index": 0, + "success": false, + } + `) + vi.expect(P.whitespace.run(' ')).toMatchInlineSnapshot(` + { + "index": 1, + "success": true, + "value": " ", + } + `) + vi.expect(P.whitespace.run(' ')).toMatchInlineSnapshot(` + { + "index": 2, + "success": true, + "value": " ", + } + `) + + vi.expect(P.alpha.run('')).toMatchInlineSnapshot(` + { + "index": 0, + "success": false, + } + `) + vi.expect(P.alpha.run('a')).toMatchInlineSnapshot(` + { + "index": 1, + "success": true, + "value": "a", + } + `) + vi.expect(P.alpha.run('1')).toMatchInlineSnapshot(` + { + "index": 0, + "success": false, + } + `) + vi.expect(P.alpha.run('ab')).toMatchInlineSnapshot(` + { + "index": 1, + "success": true, + "value": "a", + } + `) + vi.expect(P.alpha.many().run('ab')).toMatchInlineSnapshot(` + { + "index": 2, + "success": true, + "value": [ + "a", + "b", + ], + } + `) + + vi.expect(P.ident.run('hey jude')).toMatchInlineSnapshot(` + { + "index": 3, + "success": true, + "value": "hey", + } + `) + + vi.expect(parseObjectEntries.run('{ "abc": 123, "def": 456 }')).toMatchInlineSnapshot(` + { + "index": 26, + "success": true, + "value": [ + [ + "abc", + "123", + ], + [ + "def", + "456 ", + ], + ], + } + `) + + vi.expect(parseObjectEntries.run('{ "abc": 123 }')).toMatchInlineSnapshot(` + { + "index": 14, + "success": true, + "value": [ + [ + "abc", + "123 ", + ], + ], + } + `) + + vi.expect(entriesDanglingComma.run('abc: 123')).toMatchInlineSnapshot(` + { + "index": 8, + "success": true, + "value": [ + [ + "abc", + "123", + ], + ], + } + `) + + vi.expect(parseObjectEntries.run('{}')).toMatchInlineSnapshot(` + { + "index": 2, + "success": true, + "value": [], + } + `) + + vi.expect(parseObjectEntries.run("{\ + type: `equals: equals`,\ + term: 'equals: equals(x)',\ + } as const")).toMatchInlineSnapshot(` + { + "index": 82, + "success": true, + "value": [ + [ + "type", + "\`equals: equals\`", + ], + [ + "term", + "'equals: equals(x)'", + ], + ], + } + `) + + }) +}) diff --git a/packages/schema-generator/test/version.test.ts b/packages/schema-generator/test/version.test.ts index 23f422a9..7ad20cb6 100644 --- a/packages/schema-generator/test/version.test.ts +++ b/packages/schema-generator/test/version.test.ts @@ -1,10 +1,10 @@ import * as vi from 'vitest' import pkg from '../package.json' with { type: 'json' } -import { schemaGenerator } from '@traversable/schema-generator' +import { VERSION } from '@traversable/schema-generator' vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { vi.it('〖⛳️〗› ❲schemaGenerator#VERSION❳', () => { const expected = `${pkg.name}@${pkg.version}` - vi.assert.equal(schemaGenerator.VERSION, expected) + vi.assert.equal(VERSION, expected) }) }) \ No newline at end of file From 963f9afddef55e98b7d70370c81cb1850947d264 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 19:53:15 -0500 Subject: [PATCH 09/45] feat(generator): schema generator is working --- packages/derive-validators/src/exports.ts | 1 + packages/derive-validators/src/shared.ts | 11 +- packages/registry/src/exports.ts | 3 +- packages/registry/src/function.ts | 300 ++++++++ packages/registry/src/globalThis.ts | 2 +- packages/registry/src/pick.ts | 8 + packages/registry/src/types.ts | 12 + packages/schema-generator/package.json | 2 + packages/schema-generator/src/exports.ts | 26 + packages/schema-generator/src/imports.ts | 664 ++++++++++++++++++ .../src/parser-combinators.ts | 26 + packages/schema-generator/src/parser.ts | 222 ++++++ .../schema-generator/test/imports.test.ts | 86 +++ .../test/test-data/array/core.ts | 99 +++ .../test/test-data/array/equals.ts | 27 + .../test/test-data/array/toJsonSchema.ts | 37 + .../test/test-data/array/toString.ts | 22 + .../test/test-data/array/validate.ts | 48 ++ packages/schema-generator/tsconfig.test.json | 4 +- packages/schema-to-json-schema/src/exports.ts | 1 + packages/schema/src/exports.ts | 4 + packages/schema/src/namespace.ts | 2 +- packages/schema/src/schema.ts | 46 +- pnpm-lock.yaml | 6 + 24 files changed, 1633 insertions(+), 26 deletions(-) create mode 100644 packages/schema-generator/src/imports.ts create mode 100644 packages/schema-generator/src/parser.ts create mode 100644 packages/schema-generator/test/imports.test.ts create mode 100644 packages/schema-generator/test/test-data/array/core.ts create mode 100644 packages/schema-generator/test/test-data/array/equals.ts create mode 100644 packages/schema-generator/test/test-data/array/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/array/toString.ts create mode 100644 packages/schema-generator/test/test-data/array/validate.ts diff --git a/packages/derive-validators/src/exports.ts b/packages/derive-validators/src/exports.ts index 0a9ffc1d..e79a1fda 100644 --- a/packages/derive-validators/src/exports.ts +++ b/packages/derive-validators/src/exports.ts @@ -14,6 +14,7 @@ export type { } from './errors.js' export { + NULLARY as NullaryErrors, ERROR as Errors, ErrorType, dataPath as dataPathFromSchemaPath, diff --git a/packages/derive-validators/src/shared.ts b/packages/derive-validators/src/shared.ts index 57e8e6b7..3e2d04b7 100644 --- a/packages/derive-validators/src/shared.ts +++ b/packages/derive-validators/src/shared.ts @@ -1,3 +1,4 @@ +import type { Unknown } from '@traversable/registry' import { symbol } from '@traversable/registry' import type { t, SchemaOptions } from '@traversable/schema' @@ -9,15 +10,11 @@ export interface Options extends SchemaOptions { export type Validate = never | { (u: T | {} | null | undefined): true | ValidationError[] } -export type ValidationFn = never | { - (u: unknown, path?: t.Functor.Index): true | ValidationError[]; - tag: t.Tag - def?: unknown - ctx: (keyof any)[] +export type ValidationFn = never | { + (u: T | Unknown, path?: (keyof any)[]): true | ValidationError[]; } export interface Validator { validate: ValidationFn } - export const isOptional = (u: unknown): u is t.optional => - !!u && typeof u === 'function' && symbol.optional in u && typeof u[symbol.optional] === 'number' \ No newline at end of file + !!u && typeof u === 'function' && symbol.optional in u && typeof u[symbol.optional] === 'number' diff --git a/packages/registry/src/exports.ts b/packages/registry/src/exports.ts index 5b8ba624..08fe08c8 100644 --- a/packages/registry/src/exports.ts +++ b/packages/registry/src/exports.ts @@ -45,6 +45,7 @@ export { export { unsafeCompact } from './compact.js' export { + objectFromKeys, omit, omit_, omitWhere, @@ -56,4 +57,4 @@ export { export { merge, mut } from './merge.js' -export { } from './set.js' +export { ValueSet } from './set.js' diff --git a/packages/registry/src/function.ts b/packages/registry/src/function.ts index 8051d2f5..926ba536 100644 --- a/packages/registry/src/function.ts +++ b/packages/registry/src/function.ts @@ -199,3 +199,303 @@ export function flow( case args.length === 3: return function (this: unknown) { return args[2](args[1](args[0].apply(this, arguments))) } } } + +type fn = globalThis.Function +type _ = unknown + +export function pipe(): void +export function pipe(a: a): a +export function pipe(a: a, ab: (a: a) => b): b +export function pipe(a: a, ab: (a: a) => b, bc: (b: b) => c): c +export function pipe(a: a, ab: (a: a) => b, bc: (b: b) => c, cd: (c: c) => d): d +export function pipe(a: a, ab: (a: a) => b, bc: (b: b) => c, cd: (c: c) => d, de: (d: d) => e,): e +export function pipe(a: a, ab: (a: a) => b, bc: (b: b) => c, cd: (c: c) => d, de: (d: d) => e, ef: (e: e) => f): f +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, +): g +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, +): h +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, +): i +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, +): j +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, +): k +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, + kl: (k: k) => l, +): l +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, + kl: (k: k) => l, + lm: (l: l) => m, +): m +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, + kl: (k: k) => l, + lm: (l: l) => m, + mn: (m: m) => n, +): n +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, + kl: (k: k) => l, + lm: (l: l) => m, + mn: (m: m) => n, + no: (n: n) => o, +): o +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, + kl: (k: k) => l, + lm: (l: l) => m, + mn: (m: m) => n, + no: (n: n) => o, + op: (o: o) => p, +): p +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, + kl: (k: k) => l, + lm: (l: l) => m, + mn: (m: m) => n, + no: (n: n) => o, + op: (o: o) => p, + pq: (p: p) => q, +): q +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, + kl: (k: k) => l, + lm: (l: l) => m, + mn: (m: m) => n, + no: (n: n) => o, + op: (o: o) => p, + pq: (p: p) => q, + qr: (q: q) => r, +): r +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, + kl: (k: k) => l, + lm: (l: l) => m, + mn: (m: m) => n, + no: (n: n) => o, + op: (o: o) => p, + pq: (p: p) => q, + qr: (q: q) => r, + rs: (r: r) => s, +): s +export function pipe( + a: a, + ab: (a: a) => b, + bc: (b: b) => c, + cd: (c: c) => d, + de: (d: d) => e, + ef: (e: e) => f, + fg: (f: f) => g, + gh: (g: g) => h, + hi: (h: h) => i, + ij: (i: i) => j, + jk: (j: j) => k, + kl: (k: k) => l, + lm: (l: l) => m, + mn: (m: m) => n, + no: (n: n) => o, + op: (o: o) => p, + pq: (p: p) => q, + qr: (q: q) => r, + rs: (r: r) => s, + st: (s: s) => t, +): t +export function pipe( + ...a: + | [_] + | [_, fn] + | [_, fn, fn] + | [_, fn, fn, fn] + | [_, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] + | [_, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn, fn] +): unknown { + switch (true) { + case a.length === 1: + return a[0] + case a.length === 2: + return a[1](a[0]) + case a.length === 3: + return a[2](a[1](a[0])) + case a.length === 4: + return a[3](a[2](a[1](a[0]))) + case a.length === 5: + return a[4](a[3](a[2](a[1](a[0])))) + case a.length === 6: + return a[5](a[4](a[3](a[2](a[1](a[0]))))) + case a.length === 7: + return a[6](a[5](a[4](a[3](a[2](a[1](a[0])))))) + case a.length === 8: + return a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0]))))))) + case a.length === 9: + return a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0])))))))) + case a.length === 10: + return a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0]))))))))) + case a.length === 11: + return a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0])))))))))) + case a.length === 12: + return a[11](a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0]))))))))))) + case a.length === 13: + return a[12](a[11](a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0])))))))))))) + case a.length === 14: + return a[13](a[12](a[11](a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0]))))))))))))) + case a.length === 15: + return a[14](a[13](a[12](a[11](a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0])))))))))))))) + case a.length === 16: + return a[15](a[14](a[13](a[12](a[11](a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0]))))))))))))))) + case a.length === 17: + return a[16](a[15](a[14](a[13](a[12](a[11](a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0])))))))))))))))) + case a.length === 18: + return a[17](a[16](a[15](a[14](a[13](a[12](a[11](a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0]))))))))))))))))) + case a.length === 19: + return a[18](a[17](a[16](a[15](a[14](a[13](a[12](a[11](a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0])))))))))))))))))) + case a.length === 20: + return a[19](a[18](a[17](a[16](a[15](a[14](a[13](a[12](a[11](a[10](a[9](a[8](a[7](a[6](a[5](a[4](a[3](a[2](a[1](a[0]))))))))))))))))))) + default: { + const args: fn[] = a + let ret: unknown = args[0] + for (let ix = 1, len = args.length; ix < len; ix++) ret = args[ix](ret) + return ret + } + } +} diff --git a/packages/registry/src/globalThis.ts b/packages/registry/src/globalThis.ts index 2625657a..9a33fcbb 100644 --- a/packages/registry/src/globalThis.ts +++ b/packages/registry/src/globalThis.ts @@ -18,7 +18,7 @@ export const Object_entries export const Object_is = globalThis.Object.is export const Object_keys - : (x: T) => K[] + : (x: T) => (K)[] = globalThis.Object.keys export const Object_values = globalThis.Object.values diff --git a/packages/registry/src/pick.ts b/packages/registry/src/pick.ts index 19968f8b..0dc7422a 100644 --- a/packages/registry/src/pick.ts +++ b/packages/registry/src/pick.ts @@ -12,6 +12,14 @@ export type IndexOf< I extends keyof T = keyof T > = [T] extends [readonly any[]] ? Exclude : I + +export function objectFromKeys(...keys: [...T[]]): { [K in T]: K } +export function objectFromKeys(...keys: [...T[]]) { + let out: { [x: keyof any]: keyof any } = {} + for (let k of keys) out[k] = k + return out +} + export type pick = never | { [P in K]: T[P] } export declare namespace pick { type Lax = never | { [P in K as P extends keyof T ? P : never]: T[P & keyof T] } diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index 1eeb9e1f..2e490706 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -1,3 +1,5 @@ +import type { newtype } from '@traversable/registry/newtype' + export type * from './functor.js' export type * from './hkt.js' export type * from './newtype.js' @@ -10,6 +12,16 @@ export type Entry = readonly [k: string, v: T] export type Entries = readonly Entry[] export type Unknown = {} | null | undefined +/* @ts-expect-error */ +export type Key = `${T}` +export type Autocomplete = T | (string & {}) +export interface Etc { [x: string]: T[keyof T] } + +export interface Record extends newtype<{ [P in K]: V } & { [x in string]+?: V }> { } + +export interface Array extends newtype { } +export interface ReadonlyArray extends newtype { } + // transforms export type Force = never | { -readonly [K in keyof T]: T[K] } export type Intersect = X extends readonly [infer H, ...infer T] ? Intersect : _ diff --git a/packages/schema-generator/package.json b/packages/schema-generator/package.json index 4d380807..031c57e7 100644 --- a/packages/schema-generator/package.json +++ b/packages/schema-generator/package.json @@ -40,6 +40,8 @@ "@traversable/schema": "workspace:^" }, "devDependencies": { + "@traversable/derive-validators": "workspace:^", + "@traversable/schema-to-json-schema": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema": "workspace:^" } diff --git a/packages/schema-generator/src/exports.ts b/packages/schema-generator/src/exports.ts index a5658091..3473b169 100644 --- a/packages/schema-generator/src/exports.ts +++ b/packages/schema-generator/src/exports.ts @@ -1,2 +1,28 @@ export * from './version.js' export * as P from './parser-combinators.js' + +export type { + DependenciesBySchema, + ParsedImport, + ParsedImports, +} from './imports.js' +export { + deduplicateDependencies, + makeImport, + makeImports, + makeImportArraysBySchemaName, + writeSchemas, +} from './imports.js' + +export type { + ParsedSourceFile, +} from './parser.js' + +export { + createProgram, + parseFile, + parseSourceFile, + replaceExtensions, +} from './parser.js' + + diff --git a/packages/schema-generator/src/imports.ts b/packages/schema-generator/src/imports.ts new file mode 100644 index 00000000..989feb00 --- /dev/null +++ b/packages/schema-generator/src/imports.ts @@ -0,0 +1,664 @@ +import * as fs from 'node:fs' + +import { t } from '@traversable/schema' +// import '@traversable/derive-validators/install' + +import type { Comparator, Equal } from '@traversable/registry' +import { + fn, + Object_keys, + ValueSet, + Array_isArray, + has, + omit_ as omit, + pick_ as pick, +} from "@traversable/registry" + +import type { ParsedSourceFile } from './parser.js' +import { + parseFile, + replaceExtensions, +} from './parser.js' + +// TODO: move +function objectFromKeys(...keys: [...T[]]): { [K in T]: K } +function objectFromKeys(...keys: [...T[]]) { + let out: { [x: keyof any]: keyof any } = {} + for (let k of keys) out[k] = k + return out +} + +let stringEquals: Equal = (l, r) => l === r +let stringComparator: Comparator = (l, r) => l.localeCompare(r) + +let importEquals: Equal = (l, r) => { + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + else return l[0] === r[0] && l[1] === r[1] + } + else if (Array_isArray(r)) return false + else return l === r +} + +let importComparator: Comparator = (l, r) => { + if (typeof l === 'string') { + if (typeof r === 'string') return l.localeCompare(r) + else return 1 + } else if (typeof r === 'string') return -1 + else { + return l[0].localeCompare(r[0]) + } +} + +export let packageNames = [ + '@traversable/derive-validators', + '@traversable/registry', + '@traversable/schema', + '@traversable/schema-to-json-schema', +] as const satisfies any[] +export type PackageName = typeof packageNames[number] +export let PackageName = objectFromKeys(...packageNames) + +export let methodNames = [ + 'core', + 'equals', + 'toJsonSchema', + 'toString', + 'validate', +] as const satisfies any[] +export type MethodName = typeof methodNames[number] +export let MethodName = objectFromKeys(...methodNames) + +export type Config = t.typeof +export let Config = t.object(fn.map(MethodName, () => t.optional(t.eq(true)))) + +export type AliasedImport = t.typeof +export let AliasedImport = t.tuple(t.string, t.string) + +export type NamedImport = t.typeof +export let NamedImport = t.union(t.string, AliasedImport) + +export type Import = t.typeof +export let Import = t.object({ + named: t.array(NamedImport), + namespace: t.optional(t.string), +}) + +export type Imports = t.typeof +export let Imports = t.object({ type: Import, term: Import }) + +export type PackageImports = t.typeof +export let PackageImports = t.object(fn.map(PackageName, () => t.optional(Imports))) + +export type SchemaDependencies = t.typeof +export let SchemaDependencies = t.object(fn.map(MethodName, () => PackageImports)) + +export type DependenciesBySchema = t.typeof +export let DependenciesBySchema = t.record(SchemaDependencies) + +export type DeduplicatedImport = { + named: ValueSet + namespace: ValueSet +} + +export type DeduplicatedImports = { + type: DeduplicatedImport + term: DeduplicatedImport +} + +export type ParsedImport = { + named: NamedImport[] + namespace: string[] +} +export type ParsedImports = { + type: ParsedImport + term: ParsedImport +} + +export function makeArraySchema({ equals, toJsonSchema, toString, validate }: Config) { +} + +let makeSpacing = (singleLine: boolean) => ({ + bracket: singleLine ? ' ' : '\n', + indent: singleLine ? '' : ' ', + separator: singleLine ? ', ' : ',\n', + space: singleLine ? ' ' : ' ', +}) + +export function makeImport(dependency: string, { term, type }: ParsedImports, maxPerLine = 3): string[] { + let out = Array.of() + if (Array_isArray(type.namespace)) + out.push(...type.namespace.map((ns) => `import type * as ${ns} from '${dependency}'`)) + if (Array_isArray(term.namespace)) + out.push(...term.namespace.map((ns) => `import * as ${ns} from '${dependency}'`)) + if (Array.isArray(type.named) && type.named.length > 0) { + let singleLine = type.named.length <= maxPerLine + let $$ = makeSpacing(singleLine) + out.push(`import type {${$$.bracket}` + + type.named + .map((_) => typeof _ === 'string' ? `${$$.indent}${_}` : `${$$.space}${_[0]} as ${_[1]}`) + .join($$.separator) + `${$$.bracket}} from '${dependency}'`) + } + if (Array.isArray(term.named) && term.named.length > 0) { + let singleLine = term.named.length <= maxPerLine + let $$ = makeSpacing(singleLine) + out.push(`import {${singleLine ? ' ' : '\n'}` + + term.named + .map((_) => typeof _ === 'string' ? `${$$.indent}${_}` : `${$$.space}${_[0]} as ${_[1]}`) + .join($$.separator) + `${$$.bracket}} from '${dependency}'`) + } + return out +} + +let initializeImport = () => ({ named: ValueSet.new(importEquals), namespace: ValueSet.new(stringEquals) }) +let initializeImports = () => ({ type: initializeImport(), term: initializeImport() }) + +function initializeIntermediateRepresentation(): Record +function initializeIntermediateRepresentation() { + let out: { [K in PackageName]?: DeduplicatedImports } = {} + for (let name of packageNames) { + out[name] = initializeImports() + } + return out +} + +// export function deduplicateDependencies(config: Record, dependencies: DependenciesBySchema) { +// { +// [x: string]: Record<"@traversable/derive-validators" | "@traversable/registry" | "@traversable/schema" | "@traversable/schema-to-json-schema", DeduplicatedImports>; +// } +// { +// [x: string]: Record<"@traversable/derive-validators" | "@traversable/registry" | "@traversable/schema" | "@traversable/schema-to-json-schema", DeduplicatedImports>; +// } +export function deduplicateDependencies( + config: Record, + dependencies: Record> +): Record { + return fn.map( + dependencies, + (dependency) => { + let init = initializeIntermediateRepresentation() + return Object_keys(config) + .map((k) => dependency[k]) + .reduce( + (acc, dep) => { + void fn.map(acc, (accValue, k) => { + if (has(k)(dep)) { + if (has(k, 'type', 'namespace')(dep)) { + let set = accValue.type.namespace + let termlevelSet = accValue.term.namespace + let ns = dep[k].type.namespace + if (!termlevelSet.has(ns)) + set.add(ns) + } + if (has(k, 'type', 'named')(dep)) { + let set = accValue.type.named + let termlevelSet = accValue.term.named + let names = dep[k].type.named + for (let name of names) + if (!termlevelSet.has(name)) + set.add(name) + } + if (t.has(k, 'term', 'namespace', t.string)(dep)) { + let set = accValue.term.namespace + let typelevelSet = accValue.type.namespace + let ns = dep[k].term.namespace + set.add(ns) + if (typelevelSet.has(ns)) + typelevelSet.delete(ns) + } + if (t.has(k, 'term', 'named')(dep)) { + let set = accValue.term.named + let typelevelSet = accValue.type.named + let names = dep[k].term.named + for (let name of names) { + set.add(name) + if (typelevelSet.has(name)) + typelevelSet.delete(name) + } + } + } + }) + + return acc + }, + init, + ) + } + ) +} + +// export function makeImportArraysBySchemaName>>( +// config: Record, +// dependencies: T +// ): { [K in keyof T]: { [P in FK]: string[] } } + +export function makeImportArraysBySchemaName>>>( + config: { [K in keyof Schemas]: boolean }, + dependencies: Schemas +) { + if (!DependenciesBySchema(dependencies)) { + console.log('dependencies', JSON.stringify(dependencies, null, 2)) + console.error('Received invalid dependencies:', DependenciesBySchema(dependencies)) + throw Error('Received invalid dependencies; see console for full error message') + } + + let deduplicated = deduplicateDependencies(config, dependencies); + + return Object.entries(deduplicated) + .map( + ([schemaName, schemaDeps]) => [ + schemaName, fn.map( + schemaDeps, + ({ type, term }, depName) => makeImport( + depName, { + type: { + named: [...type.named.values()].sort(importComparator), + namespace: [...type.namespace.values()].sort(stringComparator), + }, + term: { + named: [...term.named.values()].sort(importComparator), + namespace: [...term.namespace.values()].sort(stringComparator), + }, + }) + ) + ] satisfies [any, any] + ) + .reduce>( + (acc, [k, v]) => (acc[k] = Object.values(v).filter((_) => _.length > 0).map((_) => _.join('\n')), acc), + {} + ) +} + +// export function makeImports, T extends { [K in keyof FK]: Record }>( +// config: FK, +// dependencies: T +// ): { [K in keyof T]: string } + +export function makeImports, T extends Record>>>( + config: FK, + dependencies: T +): { [K in keyof T]: string } + +// export function makeImports>>( +// config: Record, +// dependencies: T +// ): { [K in keyof T]: string } + +export function makeImports, T extends Record>>>( + config: FK, + dependencies: T +): { [K in keyof T]: string } + +export function makeImports( + config: Record, + dependencies: Record>> +): {} { + // console.log('\n\n\nconfig in makeImports', config, '\n\n\n') + // console.log('\n\n\ndependencies in makeImports', JSON.stringify(dependencies, null, 2), '\n\n\n') + + return fn.map( + makeImportArraysBySchemaName(config, dependencies), + (importArray) => importArray.join('\n') + ) +} + + +let isKeyOf = (k: keyof any, t: T): k is keyof T => !!t && typeof t === 'object' && k in t + + +let makeSchemaFileHeader = (schemaName: string) => [ + ` +/** + * t.${schemaName} schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +`.trim(), +].join('\n') + +let makeHeaderComment = (header: string) => [ + `///////` + '/'.repeat(header.length) + `///////`, + `/// ` + header + ` ///`, +].join('\n') + +let makeFooterComment = (footer: string) => [ + `/// ` + footer + ` ///`, + `///////` + '/'.repeat(footer.length) + `///////`, +].join('\n') + +function makeSchemaFileContent( + schemaName: string, + parsedSourceFiles: Record, + imports: string, +) { + let withoutCore = omit(parsedSourceFiles, 'core') + let extensions = fn.pipe( + fn.map(withoutCore, (source) => source.body.trim()), + Object.entries, + fn.map(([k, body]) => [ + makeHeaderComment(k), + body, + makeFooterComment(k), + ].join('\n')), + ) + let core = fn.pipe( + fn.map(withoutCore, (_) => _.extension), + (xs) => Object.values(xs).filter((_) => _ !== undefined), + (exts) => replaceExtensions( + pick(parsedSourceFiles, 'core').core.body, + exts + ), + ) + return [ + makeSchemaFileHeader(schemaName), + imports, + ...extensions.map((ext) => '\r' + ext), + '\r', + core, + ] +} + +export function generateSchemas>>( + config: Record, + sources: T, + targets: Record +): [path: string, content: string][] +export function generateSchemas( + config: Record, + sources: Record>, + targets: Record +): [path: string, content: string][] { + let parsedSourceFiles = fn.map(sources, fn.map(parseFile)) + let imports_ = fn.map(parsedSourceFiles, fn.map((_) => _.imports)) + let importsBySchemaName = makeImports({ ...config, core: true }, imports_) + let contentBySchemaName = fn.map(parsedSourceFiles, (v, k) => makeSchemaFileContent(k, v, importsBySchemaName[k])) + return Object.entries(contentBySchemaName).map(([k, content]) => { + if (!isKeyOf(k, targets)) throw Error('NO target found for schema type ' + k) + else return [targets[k], content.join('\n') + '\n'] satisfies [any, any] + }) +} + +export function writeSchemas>>( + config: Record, + sources: T, + targets: Record +): void +export function writeSchemas( + ...args: [ + config: Record, + sources: Record>, + targets: Record + ] +): void { + let schemas = generateSchemas(...args) + for (let [target, content] of schemas) { + void fs.writeFileSync(target, content) + } +} + +// import { t } from '@traversable/schema' + +// import type { +// Etc as ForExample, +// Comparator, +// Equal, +// Key, +// } from '@traversable/registry' + +// import { +// Array_isArray, +// fn, +// has, +// Object_keys, +// objectFromKeys, +// ValueSet, +// } from "@traversable/registry" + +// let stringEquals: Equal = (l, r) => l === r +// let stringComparator: Comparator = (l, r) => l.localeCompare(r) + +// let importEquals: Equal = (l, r) => { +// if (Array_isArray(l)) { +// if (!Array_isArray(r)) return false +// else return l[0] === r[0] && l[1] === r[1] +// } +// else if (Array_isArray(r)) return false +// else return l === r +// } + +// let importComparator: Comparator = (l, r) => { +// if (typeof l === 'string') { +// if (typeof r === 'string') return l.localeCompare(r) +// else return 1 +// } else if (typeof r === 'string') return -1 +// else { +// return l[0].localeCompare(r[0]) +// } +// } + +// // export let packageNames = [ +// // '@traversable/derive-validators', +// // '@traversable/registry', +// // '@traversable/schema', +// // '@traversable/schema-to-json-schema', +// // ] as const satisfies any[] +// // export type PackageName = typeof packageNames[number] +// // export let PackageName = objectFromKeys(...packageNames) + +// // export let methodNames = [ +// // 'core', +// // 'equals', +// // 'toJsonSchema', +// // 'toString', +// // 'validate', +// // ] as const satisfies any[] +// // export type MethodName = typeof methodNames[number] +// // export let MethodName = objectFromKeys(...methodNames) + +// // export type Config = t.typeof +// // export let Config = t.object(fn.map(MethodName, () => t.optional(t.eq(true)))) + +// export type AliasedImport = t.typeof +// export const AliasedImport = t.tuple(t.string, t.string) + +// export type NamedImport = t.typeof +// export const NamedImport = t.union(t.string, AliasedImport) + +// export type Import = t.typeof +// export let Import = t.object({ +// named: t.array(NamedImport), +// namespace: t.optional(t.string), +// }) + +// export type Imports = t.typeof +// export let Imports = t.object({ type: Import, term: Import }) + +// // export type PackageImports = t.typeof +// // export let PackageImports = t.object(fn.map(PackageName, () => t.optional(Imports))) + +// // export type SchemaDependencies_ = t.typeof +// // export let SchemaDependencies_ = t.object(fn.map(MethodName, () => PackageImports)) + +// export type SchemaDependencies = t.typeof +// export let SchemaDependencies = t.record(t.record(Imports)) + +// export type DependenciesBySchema = ForExample<{ +// [x in 'array']: ForExample<{ +// [x in 'toString']: ForExample<{ +// [x in `@traversable/${string}`]: Import +// }> +// }> +// }> + +// export const DependenciesBySchema = t.of((u): u is DependenciesBySchema => t.record(SchemaDependencies)(u)) + +// export type DeduplicatedImport = { +// named: ValueSet +// namespace: ValueSet +// } + +// export type DeduplicatedImports = { +// type: DeduplicatedImport +// term: DeduplicatedImport +// } + +// export type ParsedImport = { +// named: NamedImport[] +// namespace: string[] +// } +// export type ParsedImports = { +// type: ParsedImport +// term: ParsedImport +// } + +// let makeSpacing = (singleLine: boolean) => ({ +// bracket: singleLine ? ' ' : '\n', +// indent: singleLine ? '' : ' ', +// separator: singleLine ? ', ' : ',\n', +// space: singleLine ? ' ' : ' ', +// }) + +// let initializeImport = () => ({ named: ValueSet.new(importEquals), namespace: ValueSet.new(stringEquals) }) +// let initializeImports = () => ({ type: initializeImport(), term: initializeImport() }) + +// function initializeIntermediateRepresentation(...packageNames: K[]): Record +// function initializeIntermediateRepresentation(...packageNames: string[]) { +// let out: { [x: string]: DeduplicatedImports } = {} +// for (let name of packageNames) { +// out[name] = initializeImports() +// } +// return out +// } + + +// export function makeImport(dependency: string, { term, type }: ParsedImports, maxPerLine = 3): string[] { +// let out = Array.of() +// if (Array_isArray(type.namespace)) out.push(...type.namespace.map((ns) => `import type * as ${ns} from '${dependency}'`)) +// if (Array_isArray(term.namespace)) out.push(...term.namespace.map((ns) => `import * as ${ns} from '${dependency}'`)) +// if (Array.isArray(type.named) && type.named.length > 0) { +// let singleLine = type.named.length <= maxPerLine +// let $$ = makeSpacing(singleLine) +// out.push(`import type {${$$.bracket}` + +// type.named +// .map((_) => typeof _ === 'string' ? `${$$.indent}${_}` : `${$$.space}${_[0]} as ${_[1]}`) +// .join($$.separator) + `${$$.bracket}} from '${dependency}'`) +// } +// if (Array.isArray(term.named) && term.named.length > 0) { +// let singleLine = term.named.length <= maxPerLine +// let $$ = makeSpacing(singleLine) +// out.push(`import {${singleLine ? ' ' : '\n'}` + +// term.named +// .map((_) => typeof _ === 'string' ? `${$$.indent}${_}` : `${$$.space}${_[0]} as ${_[1]}`) +// .join($$.separator) + `${$$.bracket}} from '${dependency}'`) +// } +// return out +// } + +// export function deduplicateDependencies(config: Record, dependencies: DependenciesBySchema): Record> +// export function deduplicateDependencies(config: Record, dependencies: DependenciesBySchema): Record> { +// return fn.map( +// dependencies, +// (dependency) => { +// let init = initializeIntermediateRepresentation() +// return Object_keys(config) +// .map((k) => dependency[k]) +// .reduce( +// (acc, dep) => { +// void fn.map(acc, (accValue, k) => { +// if (has(k)(dep)) { +// if (has(k, 'type', 'namespace', t.string)(dep)) { +// let set = accValue.type.namespace +// let termlevelSet = accValue.term.namespace +// let ns = dep[k].type.namespace +// if (!termlevelSet.has(ns)) +// set.add(ns) +// } +// if (has(k, 'type', 'named', NamedImport)(dep)) { +// let set = accValue.type.named +// let termlevelSet = accValue.term.named +// let names = dep[k].type.named +// for (let name of names) +// if (!termlevelSet.has(name)) +// set.add(name) +// } +// if (t.has(k, 'term', 'namespace', t.string)(dep)) { +// let set = accValue.term.namespace +// let typelevelSet = accValue.type.namespace +// let ns = dep[k].term.namespace +// set.add(ns) +// if (typelevelSet.has(ns)) +// typelevelSet.delete(ns) +// } +// if (t.has(k, 'term', 'named', NamedImport)(dep)) { +// let set = accValue.term.named +// let typelevelSet = accValue.type.named +// let names = dep[k].term.named +// for (let name of names) { +// set.add(name) +// if (typelevelSet.has(name)) +// typelevelSet.delete(name) +// } +// } +// } +// }) + +// return acc +// }, +// init, +// ) +// } +// ) +// } + +// export function makeImportArraysBySchemaName( +// config: Record, +// dependencies: Record> +// ): Record + +// export function makeImportArraysBySchemaName( +// config: Record, +// dependencies: Record +// ): Record + +// export function makeImportArraysBySchemaName( +// config: Record, +// dependencies: Record +// ): Record { +// if (!DependenciesBySchema(dependencies)) { +// console.error('Received invalid dependencies:', DependenciesBySchema(dependencies)) +// throw Error('Received invalid dependencies; see console for full error message') +// } + +// return Object.entries(deduplicateDependencies(config, dependencies)) +// .map( +// ([schemaName, schemaDeps]) => [ +// schemaName, fn.map( +// schemaDeps, +// ({ type, term }, depName) => makeImport(`${depName}`, { +// type: { +// named: [...type.named.values()].sort(importComparator), +// namespace: [...type.namespace.values()].sort(stringComparator), +// }, +// term: { +// named: [...term.named.values()].sort(importComparator), +// namespace: [...term.namespace.values()].sort(stringComparator), +// }, +// }) +// ) +// ] satisfies [any, any] +// ) +// .reduce( +// (acc, [k, v]) => (acc[k] = Object.values(v).filter((_) => _.length > 0).map((_) => _.join('\n')), acc), +// {} as Record +// ) +// } + +// export function makeImports>>( +// config: Record, +// dependencies: T +// ): { [K in keyof T]: string } +// export function makeImports>>( +// config: Record, +// dependencies: Record +// ): Record { +// return fn.map(makeImportArraysBySchemaName(config, dependencies), (importArray) => importArray.join('\n')) +// } diff --git a/packages/schema-generator/src/parser-combinators.ts b/packages/schema-generator/src/parser-combinators.ts index aede890f..2b5d00f5 100644 --- a/packages/schema-generator/src/parser-combinators.ts +++ b/packages/schema-generator/src/parser-combinators.ts @@ -21,6 +21,12 @@ export type Options = { info?: string } +export type Found = { + index: number + input: string + result: Result +} + export interface ParserContext { handler(input: string, index: number, state: any): Result } @@ -82,6 +88,10 @@ export class Parser { ) => Parser = (handler, tag, info) => new Parser(handler, tag ?? '', info) + find(text: string): Found | undefined + find(text: string, state: State): Found | undefined + find(text: string, state?: { [x: string]: unknown }) { return find(this, text, state) } + many(): Parser many(options: many.Options): Parser many($: many.Options = {}): Parser { return many(this, $) } @@ -125,6 +135,22 @@ export declare namespace Parser { export type typeOf

= P extends Parser ? T : never } +function find(parser: Parser, text: string): Found | undefined +function find>(parser: Parser, text: string, state: State): Found | undefined +function find>(parser: Parser, text: string, state?: State): Found | undefined +function find(parser: Parser, text: string, state?: Record): Found | undefined { + for (let index = 0; index < text.length; index++) { + const innerState = Object.assign({}, state) + const result = parser.run(text, innerState, index) + if (result.success) return { + index, + result, + input: text, + } + } + return undefined; +} + function map(parser: Parser, f: (s: S) => T): Parser { return Parser.new((input, index, state) => { const result = parser.run(input, state, index) diff --git a/packages/schema-generator/src/parser.ts b/packages/schema-generator/src/parser.ts new file mode 100644 index 00000000..091b1871 --- /dev/null +++ b/packages/schema-generator/src/parser.ts @@ -0,0 +1,222 @@ +import * as fs from 'node:fs' +import ts from 'typescript' +import type { Imports } from './imports.js' +import * as P from './parser-combinators.js' +import { fn } from '@traversable/registry' + +export type ParsedSourceFile = { + imports: Record + body: string + extension?: { type?: string, term?: string } +} + +let typesMarker = '//<%= types %>' as const +let termsMarker = '//<%= terms %>' as const + +let LIB_FILE_NAME = '/lib/lib.d.ts' +let LIB = [ + 'interface Boolean {}', + 'interface Function {}', + 'interface CallableFunction {}', + 'interface NewableFunction {}', + 'interface IArguments {}', + 'interface Number { toExponential: any }', + 'interface Object {}', + 'interface RegExp {}', + 'interface String { charAt: any }', + 'interface Array { length: number [n: number]: T }', + 'interface ReadonlyArray {}', + 'declare const console: { log(msg: any): void }', + '/// ', +].join('\n') + +let key = P.seq( + P.optional(P.char('"')), + P.ident, + P.optional(P.char('"')), +).map(([, k]) => k) + +let propertyValue = P.char().many({ not: P.alt(P.char(','), P.char('}')) }).map((xs) => xs.join('')) + +let entry = P.seq( + key, + P.optional(P.whitespace), + P.char(':'), + P.optional(P.whitespace), + propertyValue, +).map(([key, , , , value]) => [key, value] as [k: string, v: string]) + +let comma = P.seq( + P.spaces, + P.char(','), + P.spaces, +).map((_) => _[1]) + +let entriesWithOptionalDanglingComma = P.seq( + P.seq(entry.trim(), P.char(',')).map(([_]) => _).many(), + P.optional(entry.trim()), +).map(([xs, x]) => x === null ? xs : [...xs, x]) + +let parseObjectEntries = P.index([ + P.char('{'), + P.trim(entriesWithOptionalDanglingComma), + P.char('}'), +], 1).map((_) => _ === null ? [] : _) + +export function parseFile(sourceFilePath: string): ParsedSourceFile { + let source = fs.readFileSync(sourceFilePath).toString('utf-8') + let program = createProgram(source) + /* initialize the type checker, otherwise we can't perform a traversal */ + let checker = program.getTypeChecker() + let sourceFile = program.getSourceFiles()[1] + return parseSourceFile(sourceFile) +} + +let isExportedVariable = (node: ts.Node): node is ts.VariableStatement => + ts.isVariableStatement(node) && !!node.modifiers?.some((_) => _.kind === ts.SyntaxKind.ExportKeyword) + +type ExtensionMetadata = { + start: number + end: number + node: ts.VariableDeclaration +} + +export function parseSourceFile(sourceFile: ts.SourceFile): ParsedSourceFile { + let imports: Record = {} + let bodyStart: number = 0 + let extensionMetadata: ExtensionMetadata | undefined = undefined + + void ts.forEachChild(sourceFile, (node) => { + if (isExportedVariable(node)) { + let extension = node.declarationList.declarations + .find((declaration) => declaration.name.getText() === 'extension') + if (extension) { + extensionMetadata = { + start: node.getStart(), + end: node.end, + node: extension, + } + } + } + + if (ts.isImportDeclaration(node)) { + let importClause = node.importClause + if (importClause === undefined) return void 0 + if (node.end > bodyStart) void (bodyStart = node.end) + let dependencyName = node.moduleSpecifier.getText().slice(`"`.length, -`"`.length) + void (imports[dependencyName] ??= { term: { named: [] }, type: { named: [] } }) + let dep = imports[dependencyName] + void importClause.forEachChild((importNode) => { + if (ts.isNamedImports(importNode)) { + void importNode.forEachChild((specifier) => { + if (importClause.isTypeOnly) + void dep.type.named.push(specifier.getText()) + else + void dep.term.named.push(specifier.getText()) + }) + } else if (ts.isNamespaceImport(importNode)) { + if (importClause.isTypeOnly) + void (dep.type.namespace = importNode.name.text) + else + void (dep.term.namespace = importNode.name.text) + } + }) + } + }) + + let content = sourceFile.getFullText() + let body: string + let extension: { type?: string, term?: string } | undefined = undefined + let ext: ExtensionMetadata = extensionMetadata as never + + if (typeof ext === 'object') { + let raw = content.slice(ext.start, ext.end) + let parsed = parseObjectEntries.run(raw.slice(raw.indexOf('{'))) + if (parsed.success) { + extension = fn.map(Object.fromEntries(parsed.value), removeQuotes) + } + + body = content.slice(bodyStart, ext.start) + content.slice(ext.end).trim() + } + else { + body = content.slice(bodyStart).trim() + } + + return extension === undefined + ? { imports, body } + : { imports, body, extension } +} + +let isQuoted = (text: string) => + (text.startsWith('"') && text.endsWith('"')) + || (text.startsWith('`') && text.endsWith('`')) + || (text.startsWith(`'`) && text.endsWith(`'`)) + +let removeQuotes = (text: string) => isQuoted(text) ? text.slice(1, -1) : text + +let parseTypesMarker = P.seq(P.spaces, P.string(typesMarker)).map(([ws, marker]) => [ws?.join('') ?? '', marker] satisfies [any, any]) +let parseTermsMarker = P.seq(P.spaces, P.string(termsMarker)).map(([ws, marker]) => [ws?.join('') ?? '', marker] satisfies [any, any]) + +export function replaceExtensions(source: string, extensions: { term?: string, type?: string }[]) { + let parsedTypesMarker = parseTypesMarker.find(source)?.result + let parsedTermsMarker = parseTermsMarker.find(source)?.result + + let types = !parsedTypesMarker?.success ? null : { + start: parsedTypesMarker.index - parsedTypesMarker.value[1].length, + end: parsedTypesMarker.index, + indentation: Math.max(parsedTypesMarker.value[0].length - 1, 0), + } + + let terms = !parsedTermsMarker?.success ? null : { + start: parsedTermsMarker.index - parsedTermsMarker.value[1].length, + end: parsedTermsMarker.index, + indentation: Math.max(parsedTermsMarker.value[0].length - 1, 0), + } + + if (!types) throw new Error('expected types') + if (!terms) throw new Error('expected terms') + + if (types.start < terms.start) return '' + + source.slice(0, types.start) + + extensions.map(({ type }, ix) => ix === 0 ? removeQuotes(type ?? '') : ' '.repeat(types.indentation) + removeQuotes(type ?? '')).join('\n') + + source.slice(types.end, terms.start) + + extensions.map(({ term }, ix) => ix === 0 ? removeQuotes(term ?? '') : ' '.repeat(terms.indentation) + removeQuotes(term ?? '')).join(',\n') + + source.slice(terms.end) + else return '' + + source.slice(0, terms.start) + + extensions.map(({ term }, ix) => ix === 0 ? removeQuotes(term ?? '') : ' '.repeat(terms.indentation) + removeQuotes(term ?? '')).join(',\n') + + source.slice(terms.end, types.start) + + extensions.map(({ type }, ix) => ix === 0 ? removeQuotes(type ?? '') : ' '.repeat(types.indentation) + removeQuotes(type ?? '')).join('\n') + + source.slice(types.end) +} + +export function createProgram(source: string): ts.Program { + let filename = '/source.ts' + let files = new Map() + files.set(filename, source) + files.set(LIB_FILE_NAME, LIB) + return ts.createProgram( + [filename], { + target: ts.ScriptTarget.ESNext, + module: ts.ModuleKind.ESNext, + strict: true, + noEmit: true, + isolatedModules: true, + types: [], + }, { + fileExists: (filename) => files.has(filename), + getCanonicalFileName: (f) => f.toLowerCase(), + getCurrentDirectory: () => '/', + getDefaultLibFileName: () => LIB_FILE_NAME, + getDirectories: () => [], + getNewLine: () => '\n', + getSourceFile: (filename, options) => { + let content = files.get(filename) + if (content === void 0) throw Error('missing file') + return ts.createSourceFile(filename, content, options) + }, + readFile: (filename) => files.get(filename), + useCaseSensitiveFileNames: () => false, + writeFile: () => { throw Error('unimplemented') }, + }) +} diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts new file mode 100644 index 00000000..d518a4b0 --- /dev/null +++ b/packages/schema-generator/test/imports.test.ts @@ -0,0 +1,86 @@ +import * as vi from 'vitest' +import * as path from 'node:path' +import * as fs from 'node:fs' + +import { + makeImport, + makeImports, + parseFile, + replaceExtensions, + writeSchemas, +} from '@traversable/schema-generator' + +let DIR_PATH = path.join(path.resolve(), 'packages', 'schema-generator', 'test') +let DATA_PATH = path.join(DIR_PATH, 'test-data') + +let PATH = { + __generated__: path.join(DIR_PATH, '__generated__'), + sources: { + array: { + core: path.join(DATA_PATH, 'array', 'core.ts'), + equals: path.join(DATA_PATH, 'array', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'array', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'array', 'toString.ts'), + validate: path.join(DATA_PATH, 'array', 'validate.ts'), + }, + }, + targets: { + array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), + string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), + } +} + +vi.describe('〖️⛳️〗‹‹‹ ❲make❳', () => { + vi.it('〖️⛳️〗› ❲makeImport❳', () => { + vi.expect( + makeImport('@traversable/schema', { term: { named: ['t'], namespace: [] }, type: { named: ['Predicate'], namespace: ['T'] } }).join('\n'), + ).toMatchInlineSnapshot(` + "import type * as T from '@traversable/schema' + import type { Predicate } from '@traversable/schema' + import { t } from '@traversable/schema'" + `) + + vi.expect( + makeImport('@traversable/schema', { term: { named: ['t', 'getConfig'], namespace: [] }, type: { named: ['Predicate'], namespace: ['T'] } }).join('\n'), + ).toMatchInlineSnapshot(` + "import type * as T from '@traversable/schema' + import type { Predicate } from '@traversable/schema' + import { t, getConfig } from '@traversable/schema'" + `) + }) + + // vi.it('〖️⛳️〗› ❲makeImports❳', () => { + // vi.expect(makeImports({ equals: true, toJsonSchema: true, toString: true, validate: true }, dependencies)).toMatchInlineSnapshot(` + // { + // "array": "import type { t, ValidationError, ValidationFn } from '@traversable/derive-validators' + // import type * as T from '@traversable/registry' + // import { + // Array_isArray, + // has, + // Object_is, + // URI + // } from '@traversable/registry' + // import { t } from '@traversable/schema' + // import type { SizeBounds } from '@traversable/schema-to-json-schema'", + // "string": "import type * as T from '@traversable/registry' + // import type * as U from '@traversable/registry' + // import * as V from '@traversable/registry' + // import { Object_keys, URL, whose } from '@traversable/registry' + // import type { VErr, VFn } from '@traversable/schema' + // import { s, stuff } from '@traversable/schema'", + // } + // `) + // }) +}) + +vi.describe('〖️⛳️〗‹‹‹ ❲parse❳', () => { + vi.it('〖️⛳️〗› ❲getFileImports❳', () => { + if (!fs.existsSync(DIR_PATH)) fs.mkdirSync(DIR_PATH) + if (!fs.existsSync(DATA_PATH)) { + fs.mkdirSync(DATA_PATH) + } + + if (!fs.existsSync(PATH.__generated__)) fs.mkdirSync(PATH.__generated__) + writeSchemas({ equals: true, toJsonSchema: true, toString: true, validate: true }, PATH.sources, PATH.targets) + }) +}) \ No newline at end of file diff --git a/packages/schema-generator/test/test-data/array/core.ts b/packages/schema-generator/test/test-data/array/core.ts new file mode 100644 index 00000000..29c8439f --- /dev/null +++ b/packages/schema-generator/test/test-data/array/core.ts @@ -0,0 +1,99 @@ +import { + Array_isArray, + Math_max, + Math_min, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Bounds } from '@traversable/schema' +import { t, __carryover as carryover, __within as within } from '@traversable/schema' + +export interface array extends array.core { + //<%= types %> +} + +export function array(schema: S): array +export function array(schema: S): array> +export function array(schema: S): array { return array.def(schema) } + +export namespace array { + export function def(x: S, prev?: array): array + export function def(x: S, prev?: unknown): array + export function def(x: S, prev?: array): array + export function def(x: S, prev?: unknown): array { + let proto = { + tag: URI.array, + } as { tag: URI.array, _type: S['_type' & keyof S][] } + let userDefinitions = { + //<%= terms %> + } + const arrayPredicate = t.isPredicate(x) ? array$(x) : Array_isArray + function self(src: unknown): src is array['_type'] { return arrayPredicate(src) } + self.min = function arrayMin(minLength: Min) { + return Object_assign( + boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), + { minLength }, + ) + } + self.max = function arrayMax(maxLength: Max) { + return Object_assign( + boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), + { maxLength }, + ) + } + self.between = function arrayBetween( + min: Min, + max: Max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max) + ) { + return Object_assign( + boundedArray(x, { gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) + } + self.def = x + if (t.has('minLength', t.integer)(prev)) self.minLength = prev.minLength + if (t.has('maxLength', t.integer)(prev)) self.maxLength = prev.maxLength + return Object.assign(self, { ...proto, ...userDefinitions }) + } +} + +export declare namespace array { + interface core { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: S['_type' & keyof S][] + minLength?: number + maxLength?: number + min(minLength: Min): array.Min + max(maxLength: Max): array.Max + between(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> + } + type Min + = [Self] extends [{ maxLength: number }] + ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> + : array.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> + : array.max + ; + interface min extends array { minLength: Min } + interface max extends array { maxLength: Max } + interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } + type type = never | T +} + +let array$ = (fn: (u: unknown) => u is T) => (u: unknown): u is T[] => Array_isArray(u) && u.every(fn) + +function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { + return Object_assign(function BoundedArraySchema(u: unknown) { + return Array_isArray(u) && within(bounds)(u.length) + }, carry, array(schema)) +} diff --git a/packages/schema-generator/test/test-data/array/equals.ts b/packages/schema-generator/test/test-data/array/equals.ts new file mode 100644 index 00000000..75f8d595 --- /dev/null +++ b/packages/schema-generator/test/test-data/array/equals.ts @@ -0,0 +1,27 @@ +import * as T from '@traversable/registry' +import { has, Array_isArray, Object_is } from '@traversable/registry' +import { t } from '@traversable/schema' + +export const extension = { + type: `equals: equals`, + term: 'equals: equals(x)', +} as const + +export type equals = never | T.Equal +/// +export function equals(schema: S): equals +export function equals(schema: S): equals +export function equals({ def }: { def: unknown }): T.Equal { + let equals = has('equals', (x): x is T.Equal => typeof x === 'function')(def) ? def.equals : Object_is + return (l, r) => { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + let len = l.length + if (len !== r.length) return false + for (let ix = len; ix-- !== 0;) + if (!equals(l[ix], r[ix])) return false + return true + } else return false + } +} \ No newline at end of file diff --git a/packages/schema-generator/test/test-data/array/toJsonSchema.ts b/packages/schema-generator/test/test-data/array/toJsonSchema.ts new file mode 100644 index 00000000..2389f0af --- /dev/null +++ b/packages/schema-generator/test/test-data/array/toJsonSchema.ts @@ -0,0 +1,37 @@ +import type { t } from '@traversable/schema' +import type * as T from '@traversable/registry' +import { has } from '@traversable/registry' +import type { SizeBounds } from '@traversable/schema-to-json-schema' + +export const extension = { + type: 'toJsonSchema(): toJsonSchema', + term: 'toJsonSchema: toJsonSchema(self)', +} + +export type toJsonSchema = never | T.Force< + & { type: 'array', items: T.Returns } + & T.PickIfDefined +> +export function toJsonSchema>(self: T): () => toJsonSchema +export function toJsonSchema(self: T): () => toJsonSchema +export function toJsonSchema( + { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, +): () => { + type: 'array' + items: unknown + minLength?: number + maxLength?: number +} { + return () => { + let items = has('toJsonSchema', (x) => typeof x === 'function')(def) ? def.toJsonSchema() : def + let out = { + type: 'array' as const, + items, + minLength, + maxLength, + } + if (typeof minLength !== 'number') delete out.minLength + if (typeof maxLength !== 'number') delete out.maxLength + return out + } +} diff --git a/packages/schema-generator/test/test-data/array/toString.ts b/packages/schema-generator/test/test-data/array/toString.ts new file mode 100644 index 00000000..1e79ba40 --- /dev/null +++ b/packages/schema-generator/test/test-data/array/toString.ts @@ -0,0 +1,22 @@ +export const extension = { + type: 'toString(): toString', + term: 'toString: toString(x)', +} + +/* @ts-expect-error */ +export type toString = never | `(${ReturnType})[]` +/// +export function toString(x: { def: S }): () => toString +export function toString(x: S): () => toString +export function toString({ def }: { def: unknown }) { + return () => { + let body = ( + !!def + && typeof def === 'object' + && 'toString' in def + && typeof def.toString === 'function' + ) ? def.toString() + : '${string}' + return ('(' + body + ')[]') + } +} diff --git a/packages/schema-generator/test/test-data/array/validate.ts b/packages/schema-generator/test/test-data/array/validate.ts new file mode 100644 index 00000000..8955999e --- /dev/null +++ b/packages/schema-generator/test/test-data/array/validate.ts @@ -0,0 +1,48 @@ +import type { t } from '@traversable/schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { URI } from '@traversable/registry' +import { Errors, NullaryErrors } from '@traversable/derive-validators' + +export const extension = { + type: `validate(u: this['_type'] | T.Unknown): true | ValidationError[]`, + term: `validate: validate(x)`, +} as const + +export type validate = never | ValidationFn + +export function validate( + itemsSchema: S, + bounds?: { minLength?: number, maxLength?: number } +): validate +export function validate( + itemsSchema: S, + bounds?: { minLength?: number, maxLength?: number } +): validate +// +export function validate( + { def }: { def: unknown }, + { minLength, maxLength }: { minLength?: number, maxLength?: number } = {} +): ValidationFn { + let validate = (( + !!def + && typeof def === 'object' + && 'validate' in def + && typeof def.validate === 'function' + ) ? def.validate + : () => true) + validateArray.tag = URI.array + function validateArray(u: unknown, path: (keyof any)[] = []) { + if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] + let errors = Array.of() + if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) + if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) + for (let i = 0, len = u.length; i < len; i++) { + let y = u[i] + let results = validate(y, [...path, i]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateArray +} diff --git a/packages/schema-generator/tsconfig.test.json b/packages/schema-generator/tsconfig.test.json index fd50be21..5bf59844 100644 --- a/packages/schema-generator/tsconfig.test.json +++ b/packages/schema-generator/tsconfig.test.json @@ -8,8 +8,10 @@ }, "references": [ { "path": "tsconfig.src.json" }, + { "path": "../derive-validators" }, { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema" }, + { "path": "../schema-to-json-schema" } ], "include": ["test"] } diff --git a/packages/schema-to-json-schema/src/exports.ts b/packages/schema-to-json-schema/src/exports.ts index aebc231a..ac69290b 100644 --- a/packages/schema-to-json-schema/src/exports.ts +++ b/packages/schema-to-json-schema/src/exports.ts @@ -4,3 +4,4 @@ type JsonSchema = import('./jsonSchema.js').JsonSchema export { JsonSchema } export { toJsonSchema, fromJsonSchema } from './recursive.js' export { VERSION } from './version.js' +export type * from './specification.js' diff --git a/packages/schema/src/exports.ts b/packages/schema/src/exports.ts index 4eba60d3..dff46050 100644 --- a/packages/schema/src/exports.ts +++ b/packages/schema/src/exports.ts @@ -1,5 +1,6 @@ export type { Algebra, + Array, Atoms, Coalgebra, Comparator, @@ -23,6 +24,8 @@ export type { Param, Primitive, RAlgebra, + ReadonlyArray, + Record, Returns, Showable, Tuple, @@ -61,6 +64,7 @@ export type Predicate = [T] extends [never] export { clone } from './clone.js' +export type { Bounds } from './bounded.js' export type { Guard, Typeguard, diff --git a/packages/schema/src/namespace.ts b/packages/schema/src/namespace.ts index b0b12fb9..e0bc9849 100644 --- a/packages/schema/src/namespace.ts +++ b/packages/schema/src/namespace.ts @@ -7,11 +7,11 @@ export type { F, Fixpoint, Free, + Inline, invalid, Leaf, LowerBound, Predicate, - ReadonlyArray, Schema, Tag, top, diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index 8a1c21a9..7437d9f2 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -1,5 +1,14 @@ -import type * as T from '@traversable/registry' -import type { SchemaOptions as Options, TypeError } from '@traversable/registry' +import type { + Array, + Functor as Functor_, + HKT, + Mut, + Mutable, + ReadonlyArray, + SchemaOptions as Options, + TypeError, +} from '@traversable/registry' + import { applyOptions, fn, getConfig, has, omitMethods, parseArgs, symbol, URI } from '@traversable/registry' import type { @@ -146,10 +155,10 @@ export type Fixpoint = | Leaf | Unary -export interface Free extends T.HKT { [-1]: F } +export interface Free extends HKT { [-1]: F } +export function of(typeguard: S): Entry export function of(typeguard: S): of -export function of(typeguard: S): of> export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { typeguard.def = typeguard return Object_assign(typeguard, of.prototype) @@ -487,7 +496,7 @@ const nonnullable = function NonNullableSchema(src: unknown) { retu nonnullable.tag = URI.nonnullable nonnullable.def = {} -export function eq>(value: V, options?: Options): eq> +export function eq>(value: V, options?: Options): eq> export function eq(value: V, options?: Options): eq export function eq(value: V, options?: Options): eq { return eq.def(value, options) } export interface eq { (u: unknown): u is V, tag: URI.eq, def: V, _type: V } @@ -532,7 +541,7 @@ export namespace optional { = has('tag', eq(URI.optional)) as never } -export function array(schema: S, readonly: 'readonly'): ReadonlyArray +export function array(schema: S, readonly: 'readonly'): readonlyArray export function array(schema: S): array export function array(schema: S): array> export function array(schema: S): array { return array.def(schema) } @@ -540,7 +549,7 @@ export interface array extends array.methods { (u: unknown): u is this['_type'] tag: URI.array def: S - _type: S['_type' & keyof S][] + _type: Array minLength?: number maxLength?: number } @@ -563,7 +572,7 @@ export declare namespace array { interface min extends array { minLength: Min } interface max extends array { maxLength: Max } interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } - type type = never | T + type type> = never | T } export namespace array { export let prototype = { tag: URI.array } as array @@ -606,20 +615,25 @@ export namespace array { } export const readonlyArray: { - (schema: S, readonly: 'readonly'): ReadonlyArray - (schema: S): ReadonlyArray> + (schema: S, readonly: 'readonly'): readonlyArray + (schema: S): readonlyArray> } = array -export interface ReadonlyArray { +export interface readonlyArray { (u: unknown): u is this['_type'] tag: URI.array def: S - _type: readonly S['_type' & keyof S][] + _type: ReadonlyArray } export function record(schema: S): record export function record(schema: S): record> export function record(schema: S) { return record.def(schema) } -export interface record { (u: unknown): u is this['_type'], tag: URI.record, def: S, _type: Record } +export interface record { + (u: unknown): u is this['_type'] + tag: URI.record + def: S + _type: Record +} export namespace record { export let prototype = { tag: URI.record } as record export type type> = never | T @@ -784,7 +798,7 @@ export const isUnary = (u: unknown): u is Unary => hasTag(u) && unaryTags.includ export const isCore = (u: unknown): u is Schema => hasTag(u) && tags.includes(u.tag as never) export declare namespace Functor { type Index = (keyof any)[] } -export const Functor: T.Functor = { +export const Functor: Functor_ = { map(f) { type T = ReturnType return (x) => { @@ -804,7 +818,7 @@ export const Functor: T.Functor = { } } -export const IndexedFunctor: T.Functor.Ix = { +export const IndexedFunctor: Functor_.Ix = { ...Functor, mapWithIndex(f) { type T = ReturnType @@ -868,3 +882,5 @@ function boundedArray(schema: S, bounds: Bounds, carry?: {}): return Array_isArray(u) && within(bounds)(u.length) }, carry, array(schema)) } + +array(number_)._type diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6b4dd879..326a5cd7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -260,12 +260,18 @@ importers: packages/schema-generator: devDependencies: + '@traversable/derive-validators': + specifier: workspace:^ + version: link:../derive-validators/dist '@traversable/registry': specifier: workspace:^ version: link:../registry/dist '@traversable/schema': specifier: workspace:^ version: link:../schema/dist + '@traversable/schema-to-json-schema': + specifier: workspace:^ + version: link:../schema-to-json-schema/dist publishDirectory: dist packages/schema-seed: From dc4d5ddbab4aa1736a59ecba6a9d7a8ed5299595 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 19:54:30 -0500 Subject: [PATCH 10/45] chore: commits changeset --- .changeset/eager-brooms-carry.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/eager-brooms-carry.md diff --git a/.changeset/eager-brooms-carry.md b/.changeset/eager-brooms-carry.md new file mode 100644 index 00000000..889c0d4c --- /dev/null +++ b/.changeset/eager-brooms-carry.md @@ -0,0 +1,9 @@ +--- +"@traversable/schema-to-json-schema": patch +"@traversable/derive-validators": patch +"@traversable/schema-generator": patch +"@traversable/registry": patch +"@traversable/schema": patch +--- + +feat(generator): adds a shadcn-like schema generator feature From 9d7793c044d28250e4d935cfdbd16f247a0df267 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Tue, 8 Apr 2025 20:40:52 -0500 Subject: [PATCH 11/45] fix build --- packages/derive-validators/src/index.ts | 2 +- packages/derive-validators/src/prototype.ts | 3 +-- packages/derive-validators/src/shared.ts | 2 +- packages/registry/src/typeName.ts | 1 + packages/schema-generator/src/__generated__/__manifest__.ts | 2 ++ 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/derive-validators/src/index.ts b/packages/derive-validators/src/index.ts index e91c4858..e2d2295d 100644 --- a/packages/derive-validators/src/index.ts +++ b/packages/derive-validators/src/index.ts @@ -1,3 +1,3 @@ export * from './exports.js' export * as Validator from './exports.js' -export type Validator = import('./shared.js').Validator +export type Validator = import('./shared.js').Validator diff --git a/packages/derive-validators/src/prototype.ts b/packages/derive-validators/src/prototype.ts index 3b2aaf38..d5e5eaa6 100644 --- a/packages/derive-validators/src/prototype.ts +++ b/packages/derive-validators/src/prototype.ts @@ -1,10 +1,9 @@ -import { Equal, omitMethods, Primitive, typeName, URI } from '@traversable/registry' +import { Equal, Primitive, typeName, URI } from '@traversable/registry' import { t, getConfig } from '@traversable/schema' import type { ValidationError } from './errors.js' import { NULLARY, UNARY, ERROR } from './errors.js' import type { Validator } from './shared.js' -// import { isOptional } from './shared.js' export { validateNever as never, diff --git a/packages/derive-validators/src/shared.ts b/packages/derive-validators/src/shared.ts index 3e2d04b7..e0bd1501 100644 --- a/packages/derive-validators/src/shared.ts +++ b/packages/derive-validators/src/shared.ts @@ -14,7 +14,7 @@ export type ValidationFn = never | { (u: T | Unknown, path?: (keyof any)[]): true | ValidationError[]; } -export interface Validator { validate: ValidationFn } +export interface Validator { validate: ValidationFn } export const isOptional = (u: unknown): u is t.optional => !!u && typeof u === 'function' && symbol.optional in u && typeof u[symbol.optional] === 'number' diff --git a/packages/registry/src/typeName.ts b/packages/registry/src/typeName.ts index 51146d04..fc9a1782 100644 --- a/packages/registry/src/typeName.ts +++ b/packages/registry/src/typeName.ts @@ -2,4 +2,5 @@ import { NS } from './uri.js' export type TypeName = never | T extends `${NS}${infer S}` ? S : never export function typeName(x: T): TypeName +export function typeName(x: T): string export function typeName(x: { tag: string }) { return x.tag.substring(NS.length) } diff --git a/packages/schema-generator/src/__generated__/__manifest__.ts b/packages/schema-generator/src/__generated__/__manifest__.ts index eb0e557c..16187ea9 100644 --- a/packages/schema-generator/src/__generated__/__manifest__.ts +++ b/packages/schema-generator/src/__generated__/__manifest__.ts @@ -40,6 +40,8 @@ export default { "@traversable/schema": "workspace:^" }, "devDependencies": { + "@traversable/derive-validators": "workspace:^", + "@traversable/schema-to-json-schema": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema": "workspace:^" } From 7c8ee739e601b5eddf93c873348d4bc90b838ee7 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 9 Apr 2025 13:23:04 -0500 Subject: [PATCH 12/45] feat(generator): generates unary schemas --- packages/registry/src/types.ts | 2 +- packages/schema-generator/src/exports.ts | 7 +- packages/schema-generator/src/imports.ts | 624 ++++-------------- packages/schema-generator/src/parser.ts | 196 ++++-- .../schema-generator/test/imports.test.ts | 180 ++++- .../test/test-data/array/core.ts | 20 +- .../test/test-data/array/equals.ts | 20 +- .../test/test-data/array/extension.ts | 18 + .../test/test-data/array/toJsonSchema.ts | 15 +- .../test/test-data/array/toString.ts | 14 +- .../test/test-data/array/validate.ts | 14 +- .../test/test-data/object/core.ts | 0 .../test/test-data/object/equals.ts | 0 .../test/test-data/object/extension.ts | 0 .../test/test-data/object/toJsonSchema.ts | 0 .../test/test-data/object/toString.ts | 0 .../test/test-data/object/validate.ts | 0 .../test/test-data/string/core.ts | 84 +++ .../test/test-data/string/equals.ts | 1 + .../test/test-data/string/extension.ts | 20 + .../test/test-data/string/toJsonSchema.ts | 16 + .../test/test-data/string/toString.ts | 1 + .../test/test-data/string/validate.ts | 6 + packages/schema/src/namespace.ts | 1 + 24 files changed, 614 insertions(+), 625 deletions(-) create mode 100644 packages/schema-generator/test/test-data/array/extension.ts create mode 100644 packages/schema-generator/test/test-data/object/core.ts create mode 100644 packages/schema-generator/test/test-data/object/equals.ts create mode 100644 packages/schema-generator/test/test-data/object/extension.ts create mode 100644 packages/schema-generator/test/test-data/object/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/object/toString.ts create mode 100644 packages/schema-generator/test/test-data/object/validate.ts create mode 100644 packages/schema-generator/test/test-data/string/core.ts create mode 100644 packages/schema-generator/test/test-data/string/equals.ts create mode 100644 packages/schema-generator/test/test-data/string/extension.ts create mode 100644 packages/schema-generator/test/test-data/string/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/string/toString.ts create mode 100644 packages/schema-generator/test/test-data/string/validate.ts diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index 2e490706..53d645ec 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -17,7 +17,7 @@ export type Key = `${T}` export type Autocomplete = T | (string & {}) export interface Etc { [x: string]: T[keyof T] } -export interface Record extends newtype<{ [P in K]: V } & { [x in string]+?: V }> { } +export interface Record extends newtype<{ [P in K]+?: V } & { [x in string]+?: V }> { } export interface Array extends newtype { } export interface ReadonlyArray extends newtype { } diff --git a/packages/schema-generator/src/exports.ts b/packages/schema-generator/src/exports.ts index 3473b169..4fdcc190 100644 --- a/packages/schema-generator/src/exports.ts +++ b/packages/schema-generator/src/exports.ts @@ -2,15 +2,16 @@ export * from './version.js' export * as P from './parser-combinators.js' export type { - DependenciesBySchema, + ExtensionsBySchemaName, ParsedImport, ParsedImports, } from './imports.js' + export { - deduplicateDependencies, + deduplicateImports, makeImport, makeImports, - makeImportArraysBySchemaName, + makeImportsBySchemaName, writeSchemas, } from './imports.js' diff --git a/packages/schema-generator/src/imports.ts b/packages/schema-generator/src/imports.ts index 989feb00..d8ea3437 100644 --- a/packages/schema-generator/src/imports.ts +++ b/packages/schema-generator/src/imports.ts @@ -1,104 +1,56 @@ import * as fs from 'node:fs' +import type * as T from '@traversable/registry' import { t } from '@traversable/schema' -// import '@traversable/derive-validators/install' -import type { Comparator, Equal } from '@traversable/registry' +import type { + Comparator, + Etc as ForExample, +} from '@traversable/registry' import { fn, - Object_keys, - ValueSet, Array_isArray, - has, omit_ as omit, pick_ as pick, } from "@traversable/registry" import type { ParsedSourceFile } from './parser.js' import { + parseExtensionFile, parseFile, replaceExtensions, } from './parser.js' -// TODO: move -function objectFromKeys(...keys: [...T[]]): { [K in T]: K } -function objectFromKeys(...keys: [...T[]]) { - let out: { [x: keyof any]: keyof any } = {} - for (let k of keys) out[k] = k - return out -} - -let stringEquals: Equal = (l, r) => l === r let stringComparator: Comparator = (l, r) => l.localeCompare(r) -let importEquals: Equal = (l, r) => { - if (Array_isArray(l)) { - if (!Array_isArray(r)) return false - else return l[0] === r[0] && l[1] === r[1] - } - else if (Array_isArray(r)) return false - else return l === r -} - -let importComparator: Comparator = (l, r) => { - if (typeof l === 'string') { - if (typeof r === 'string') return l.localeCompare(r) - else return 1 - } else if (typeof r === 'string') return -1 - else { - return l[0].localeCompare(r[0]) - } -} - -export let packageNames = [ - '@traversable/derive-validators', - '@traversable/registry', - '@traversable/schema', - '@traversable/schema-to-json-schema', -] as const satisfies any[] -export type PackageName = typeof packageNames[number] -export let PackageName = objectFromKeys(...packageNames) - -export let methodNames = [ - 'core', - 'equals', - 'toJsonSchema', - 'toString', - 'validate', -] as const satisfies any[] -export type MethodName = typeof methodNames[number] -export let MethodName = objectFromKeys(...methodNames) - -export type Config = t.typeof -export let Config = t.object(fn.map(MethodName, () => t.optional(t.eq(true)))) - -export type AliasedImport = t.typeof -export let AliasedImport = t.tuple(t.string, t.string) - -export type NamedImport = t.typeof -export let NamedImport = t.union(t.string, AliasedImport) - export type Import = t.typeof export let Import = t.object({ - named: t.array(NamedImport), + named: t.array(t.string), namespace: t.optional(t.string), }) export type Imports = t.typeof export let Imports = t.object({ type: Import, term: Import }) -export type PackageImports = t.typeof -export let PackageImports = t.object(fn.map(PackageName, () => t.optional(Imports))) - export type SchemaDependencies = t.typeof -export let SchemaDependencies = t.object(fn.map(MethodName, () => PackageImports)) +export let SchemaDependencies = t.record(t.record(Imports)) -export type DependenciesBySchema = t.typeof -export let DependenciesBySchema = t.record(SchemaDependencies) +export type ExtensionsBySchemaName = T.Record< + 'array' | 'string', + T.Record< + 'core' | 'equals', + T.Record< + '@traversable/registry' | '@traversable/schema', + Imports + > + > +> + +export const ExtensionsBySchemaName = t.record(SchemaDependencies) export type DeduplicatedImport = { - named: ValueSet - namespace: ValueSet + named: Set + namespace: Set } export type DeduplicatedImports = { @@ -107,7 +59,7 @@ export type DeduplicatedImports = { } export type ParsedImport = { - named: NamedImport[] + named: string[] namespace: string[] } export type ParsedImports = { @@ -115,9 +67,6 @@ export type ParsedImports = { term: ParsedImport } -export function makeArraySchema({ equals, toJsonSchema, toString, validate }: Config) { -} - let makeSpacing = (singleLine: boolean) => ({ bracket: singleLine ? ' ' : '\n', indent: singleLine ? '' : ' ', @@ -150,162 +99,105 @@ export function makeImport(dependency: string, { term, type }: ParsedImports, ma return out } -let initializeImport = () => ({ named: ValueSet.new(importEquals), namespace: ValueSet.new(stringEquals) }) -let initializeImports = () => ({ type: initializeImport(), term: initializeImport() }) - -function initializeIntermediateRepresentation(): Record -function initializeIntermediateRepresentation() { - let out: { [K in PackageName]?: DeduplicatedImports } = {} - for (let name of packageNames) { - out[name] = initializeImports() +let getDependenciesFromImportsForSchema = (schemaExtensions: ExtensionsBySchemaName[keyof ExtensionsBySchemaName]) => { + if (!schemaExtensions) return [] + else { + let xs = Object.values(schemaExtensions) + .filter((_) => !!_) + .flatMap((_) => Object.keys(_).filter((_) => _.startsWith('@traversable/'))) + return Array.from(new Set(xs)) } - return out } -// export function deduplicateDependencies(config: Record, dependencies: DependenciesBySchema) { -// { -// [x: string]: Record<"@traversable/derive-validators" | "@traversable/registry" | "@traversable/schema" | "@traversable/schema-to-json-schema", DeduplicatedImports>; -// } -// { -// [x: string]: Record<"@traversable/derive-validators" | "@traversable/registry" | "@traversable/schema" | "@traversable/schema-to-json-schema", DeduplicatedImports>; -// } -export function deduplicateDependencies( - config: Record, - dependencies: Record> -): Record { +export function deduplicateImports(extensionsBySchemaName: ExtensionsBySchemaName): Record> { return fn.map( - dependencies, - (dependency) => { - let init = initializeIntermediateRepresentation() - return Object_keys(config) - .map((k) => dependency[k]) - .reduce( - (acc, dep) => { - void fn.map(acc, (accValue, k) => { - if (has(k)(dep)) { - if (has(k, 'type', 'namespace')(dep)) { - let set = accValue.type.namespace - let termlevelSet = accValue.term.namespace - let ns = dep[k].type.namespace - if (!termlevelSet.has(ns)) - set.add(ns) - } - if (has(k, 'type', 'named')(dep)) { - let set = accValue.type.named - let termlevelSet = accValue.term.named - let names = dep[k].type.named - for (let name of names) - if (!termlevelSet.has(name)) - set.add(name) - } - if (t.has(k, 'term', 'namespace', t.string)(dep)) { - let set = accValue.term.namespace - let typelevelSet = accValue.type.namespace - let ns = dep[k].term.namespace - set.add(ns) - if (typelevelSet.has(ns)) - typelevelSet.delete(ns) - } - if (t.has(k, 'term', 'named')(dep)) { - let set = accValue.term.named - let typelevelSet = accValue.type.named - let names = dep[k].term.named - for (let name of names) { - set.add(name) - if (typelevelSet.has(name)) - typelevelSet.delete(name) - } - } - } - }) - - return acc + extensionsBySchemaName, + (extension) => { + if (!extension) return {} + + let init: Record = {} + let pkgNames = getDependenciesFromImportsForSchema(extension) + + for (let pkgName of pkgNames) { + init[pkgName] = { + type: { + named: new Set(), + namespace: new Set(), + }, + term: { + named: new Set(), + namespace: new Set(), }, - init, - ) + } + } + + fn.map(extension, (imports) => { + if (!imports) return {} + + fn.map(imports, (imports, pkgName) => { + if (!`${pkgName}`.startsWith('@traversable/')) return {} + if (!imports) return {} + let { type, term } = imports + + for (let name of type.named) { + if (!init[pkgName].term.named.has(name)) + init[pkgName].type.named.add(name) + } + if (t.string(type.namespace)) { + if (!init[pkgName].term.namespace.has(type.namespace)) + init[pkgName].type.namespace.add(type.namespace) + } + for (let name of term.named) { + if (init[pkgName].type.named.has(name)) init[pkgName].type.named.delete(name) + init[pkgName].term.named.add(name) + } + if (t.string(term.namespace)) { + if (init[pkgName].type.namespace.has(term.namespace)) init[pkgName].type.namespace.delete(term.namespace) + init[pkgName].term.namespace.add(term.namespace) + } + }) + }) + + return init } ) } -// export function makeImportArraysBySchemaName>>( -// config: Record, -// dependencies: T -// ): { [K in keyof T]: { [P in FK]: string[] } } - -export function makeImportArraysBySchemaName>>>( - config: { [K in keyof Schemas]: boolean }, - dependencies: Schemas -) { - if (!DependenciesBySchema(dependencies)) { - console.log('dependencies', JSON.stringify(dependencies, null, 2)) - console.error('Received invalid dependencies:', DependenciesBySchema(dependencies)) - throw Error('Received invalid dependencies; see console for full error message') - } - - let deduplicated = deduplicateDependencies(config, dependencies); - - return Object.entries(deduplicated) - .map( - ([schemaName, schemaDeps]) => [ - schemaName, fn.map( - schemaDeps, - ({ type, term }, depName) => makeImport( - depName, { - type: { - named: [...type.named.values()].sort(importComparator), - namespace: [...type.namespace.values()].sort(stringComparator), - }, - term: { - named: [...term.named.values()].sort(importComparator), - namespace: [...term.namespace.values()].sort(stringComparator), - }, - }) - ) - ] satisfies [any, any] - ) +export function makeImportsBySchemaName(extensionsBySchemaName: S) { + return Object.entries(deduplicateImports(extensionsBySchemaName)) + .map(([schemaName, schemaDeps]) => [ + schemaName, + fn.map( + schemaDeps, + ({ type, term }, depName) => makeImport( + depName, { + type: { + named: [...type.named.values()].sort(stringComparator), + namespace: [...type.namespace.values()].sort(stringComparator), + }, + term: { + named: [...term.named.values()].sort(stringComparator), + namespace: [...term.namespace.values()].sort(stringComparator), + }, + }) + ) + ] satisfies [any, any]) .reduce>( (acc, [k, v]) => (acc[k] = Object.values(v).filter((_) => _.length > 0).map((_) => _.join('\n')), acc), {} ) } -// export function makeImports, T extends { [K in keyof FK]: Record }>( -// config: FK, -// dependencies: T -// ): { [K in keyof T]: string } - -export function makeImports, T extends Record>>>( - config: FK, - dependencies: T -): { [K in keyof T]: string } - -// export function makeImports>>( -// config: Record, -// dependencies: T -// ): { [K in keyof T]: string } - -export function makeImports, T extends Record>>>( - config: FK, - dependencies: T -): { [K in keyof T]: string } - -export function makeImports( - config: Record, - dependencies: Record>> -): {} { - // console.log('\n\n\nconfig in makeImports', config, '\n\n\n') - // console.log('\n\n\ndependencies in makeImports', JSON.stringify(dependencies, null, 2), '\n\n\n') - +export function makeImports(extensionsBySchemaName: S): { [K in keyof S]: string } +export function makeImports(extensionsBySchemaName: ExtensionsBySchemaName): {} { return fn.map( - makeImportArraysBySchemaName(config, dependencies), - (importArray) => importArray.join('\n') + makeImportsBySchemaName(extensionsBySchemaName), + (importArray) => importArray.join('\n'), ) } - let isKeyOf = (k: keyof any, t: T): k is keyof T => !!t && typeof t === 'object' && k in t - let makeSchemaFileHeader = (schemaName: string) => [ ` /** @@ -328,10 +220,12 @@ let makeFooterComment = (footer: string) => [ function makeSchemaFileContent( schemaName: string, parsedSourceFiles: Record, + parsedExtensionFile: { term?: string, type?: string }, imports: string, ) { let withoutCore = omit(parsedSourceFiles, 'core') - let extensions = fn.pipe( + let core = replaceExtensions(pick(parsedSourceFiles, 'core').core.body, parsedExtensionFile) + let files = fn.pipe( fn.map(withoutCore, (source) => source.body.trim()), Object.entries, fn.map(([k, body]) => [ @@ -340,325 +234,53 @@ function makeSchemaFileContent( makeFooterComment(k), ].join('\n')), ) - let core = fn.pipe( - fn.map(withoutCore, (_) => _.extension), - (xs) => Object.values(xs).filter((_) => _ !== undefined), - (exts) => replaceExtensions( - pick(parsedSourceFiles, 'core').core.body, - exts - ), - ) + return [ makeSchemaFileHeader(schemaName), imports, - ...extensions.map((ext) => '\r' + ext), + ...files.map((ext) => '\r' + ext), '\r', core, ] } export function generateSchemas>>( - config: Record, sources: T, targets: Record ): [path: string, content: string][] export function generateSchemas( - config: Record, sources: Record>, targets: Record ): [path: string, content: string][] { let parsedSourceFiles = fn.map(sources, fn.map(parseFile)) - let imports_ = fn.map(parsedSourceFiles, fn.map((_) => _.imports)) - let importsBySchemaName = makeImports({ ...config, core: true }, imports_) - let contentBySchemaName = fn.map(parsedSourceFiles, (v, k) => makeSchemaFileContent(k, v, importsBySchemaName[k])) + let noExts = fn.map(parsedSourceFiles, (src) => omit(src, 'extension')) + let exts = fn.map(sources, (src) => pick(src, 'extension').extension) + let parsedExtensionFiles = fn.map(exts, parseExtensionFile) + let importsBySchemaName = makeImports(fn.map(parsedSourceFiles, fn.map((_) => _.imports))) + let contentBySchemaName = fn.map( + noExts, + (v, k) => makeSchemaFileContent(k, v, parsedExtensionFiles[k], importsBySchemaName[k]) + ) + return Object.entries(contentBySchemaName).map(([k, content]) => { if (!isKeyOf(k, targets)) throw Error('NO target found for schema type ' + k) else return [targets[k], content.join('\n') + '\n'] satisfies [any, any] }) } -export function writeSchemas>>( - config: Record, - sources: T, - targets: Record -): void -export function writeSchemas( - ...args: [ - config: Record, - sources: Record>, - targets: Record - ] -): void { +export function writeSchemas>>(sources: T, targets: Record): void +export function writeSchemas(...args: [sources: Record>, targets: Record]): void { let schemas = generateSchemas(...args) for (let [target, content] of schemas) { void fs.writeFileSync(target, content) } } -// import { t } from '@traversable/schema' - -// import type { -// Etc as ForExample, -// Comparator, -// Equal, -// Key, -// } from '@traversable/registry' - -// import { -// Array_isArray, -// fn, -// has, -// Object_keys, -// objectFromKeys, -// ValueSet, -// } from "@traversable/registry" - -// let stringEquals: Equal = (l, r) => l === r -// let stringComparator: Comparator = (l, r) => l.localeCompare(r) - -// let importEquals: Equal = (l, r) => { -// if (Array_isArray(l)) { -// if (!Array_isArray(r)) return false -// else return l[0] === r[0] && l[1] === r[1] -// } -// else if (Array_isArray(r)) return false -// else return l === r -// } - -// let importComparator: Comparator = (l, r) => { -// if (typeof l === 'string') { -// if (typeof r === 'string') return l.localeCompare(r) -// else return 1 -// } else if (typeof r === 'string') return -1 -// else { -// return l[0].localeCompare(r[0]) -// } -// } - -// // export let packageNames = [ -// // '@traversable/derive-validators', -// // '@traversable/registry', -// // '@traversable/schema', -// // '@traversable/schema-to-json-schema', -// // ] as const satisfies any[] -// // export type PackageName = typeof packageNames[number] -// // export let PackageName = objectFromKeys(...packageNames) - -// // export let methodNames = [ -// // 'core', -// // 'equals', -// // 'toJsonSchema', -// // 'toString', -// // 'validate', -// // ] as const satisfies any[] -// // export type MethodName = typeof methodNames[number] -// // export let MethodName = objectFromKeys(...methodNames) - -// // export type Config = t.typeof -// // export let Config = t.object(fn.map(MethodName, () => t.optional(t.eq(true)))) - -// export type AliasedImport = t.typeof -// export const AliasedImport = t.tuple(t.string, t.string) - -// export type NamedImport = t.typeof -// export const NamedImport = t.union(t.string, AliasedImport) - -// export type Import = t.typeof -// export let Import = t.object({ -// named: t.array(NamedImport), -// namespace: t.optional(t.string), -// }) - -// export type Imports = t.typeof -// export let Imports = t.object({ type: Import, term: Import }) - -// // export type PackageImports = t.typeof -// // export let PackageImports = t.object(fn.map(PackageName, () => t.optional(Imports))) - -// // export type SchemaDependencies_ = t.typeof -// // export let SchemaDependencies_ = t.object(fn.map(MethodName, () => PackageImports)) - -// export type SchemaDependencies = t.typeof -// export let SchemaDependencies = t.record(t.record(Imports)) - -// export type DependenciesBySchema = ForExample<{ -// [x in 'array']: ForExample<{ -// [x in 'toString']: ForExample<{ -// [x in `@traversable/${string}`]: Import -// }> -// }> -// }> - -// export const DependenciesBySchema = t.of((u): u is DependenciesBySchema => t.record(SchemaDependencies)(u)) - -// export type DeduplicatedImport = { -// named: ValueSet -// namespace: ValueSet -// } - -// export type DeduplicatedImports = { -// type: DeduplicatedImport -// term: DeduplicatedImport -// } - -// export type ParsedImport = { -// named: NamedImport[] -// namespace: string[] -// } -// export type ParsedImports = { -// type: ParsedImport -// term: ParsedImport -// } - -// let makeSpacing = (singleLine: boolean) => ({ -// bracket: singleLine ? ' ' : '\n', -// indent: singleLine ? '' : ' ', -// separator: singleLine ? ', ' : ',\n', -// space: singleLine ? ' ' : ' ', -// }) - -// let initializeImport = () => ({ named: ValueSet.new(importEquals), namespace: ValueSet.new(stringEquals) }) -// let initializeImports = () => ({ type: initializeImport(), term: initializeImport() }) - -// function initializeIntermediateRepresentation(...packageNames: K[]): Record -// function initializeIntermediateRepresentation(...packageNames: string[]) { -// let out: { [x: string]: DeduplicatedImports } = {} -// for (let name of packageNames) { -// out[name] = initializeImports() -// } -// return out -// } - - -// export function makeImport(dependency: string, { term, type }: ParsedImports, maxPerLine = 3): string[] { -// let out = Array.of() -// if (Array_isArray(type.namespace)) out.push(...type.namespace.map((ns) => `import type * as ${ns} from '${dependency}'`)) -// if (Array_isArray(term.namespace)) out.push(...term.namespace.map((ns) => `import * as ${ns} from '${dependency}'`)) -// if (Array.isArray(type.named) && type.named.length > 0) { -// let singleLine = type.named.length <= maxPerLine -// let $$ = makeSpacing(singleLine) -// out.push(`import type {${$$.bracket}` + -// type.named -// .map((_) => typeof _ === 'string' ? `${$$.indent}${_}` : `${$$.space}${_[0]} as ${_[1]}`) -// .join($$.separator) + `${$$.bracket}} from '${dependency}'`) -// } -// if (Array.isArray(term.named) && term.named.length > 0) { -// let singleLine = term.named.length <= maxPerLine -// let $$ = makeSpacing(singleLine) -// out.push(`import {${singleLine ? ' ' : '\n'}` + -// term.named -// .map((_) => typeof _ === 'string' ? `${$$.indent}${_}` : `${$$.space}${_[0]} as ${_[1]}`) -// .join($$.separator) + `${$$.bracket}} from '${dependency}'`) -// } -// return out -// } - -// export function deduplicateDependencies(config: Record, dependencies: DependenciesBySchema): Record> -// export function deduplicateDependencies(config: Record, dependencies: DependenciesBySchema): Record> { -// return fn.map( -// dependencies, -// (dependency) => { -// let init = initializeIntermediateRepresentation() -// return Object_keys(config) -// .map((k) => dependency[k]) -// .reduce( -// (acc, dep) => { -// void fn.map(acc, (accValue, k) => { -// if (has(k)(dep)) { -// if (has(k, 'type', 'namespace', t.string)(dep)) { -// let set = accValue.type.namespace -// let termlevelSet = accValue.term.namespace -// let ns = dep[k].type.namespace -// if (!termlevelSet.has(ns)) -// set.add(ns) -// } -// if (has(k, 'type', 'named', NamedImport)(dep)) { -// let set = accValue.type.named -// let termlevelSet = accValue.term.named -// let names = dep[k].type.named -// for (let name of names) -// if (!termlevelSet.has(name)) -// set.add(name) -// } -// if (t.has(k, 'term', 'namespace', t.string)(dep)) { -// let set = accValue.term.namespace -// let typelevelSet = accValue.type.namespace -// let ns = dep[k].term.namespace -// set.add(ns) -// if (typelevelSet.has(ns)) -// typelevelSet.delete(ns) -// } -// if (t.has(k, 'term', 'named', NamedImport)(dep)) { -// let set = accValue.term.named -// let typelevelSet = accValue.type.named -// let names = dep[k].term.named -// for (let name of names) { -// set.add(name) -// if (typelevelSet.has(name)) -// typelevelSet.delete(name) -// } -// } -// } -// }) - -// return acc -// }, -// init, -// ) -// } -// ) -// } - -// export function makeImportArraysBySchemaName( -// config: Record, -// dependencies: Record> -// ): Record - -// export function makeImportArraysBySchemaName( -// config: Record, -// dependencies: Record -// ): Record - -// export function makeImportArraysBySchemaName( -// config: Record, -// dependencies: Record -// ): Record { -// if (!DependenciesBySchema(dependencies)) { -// console.error('Received invalid dependencies:', DependenciesBySchema(dependencies)) -// throw Error('Received invalid dependencies; see console for full error message') -// } - -// return Object.entries(deduplicateDependencies(config, dependencies)) -// .map( -// ([schemaName, schemaDeps]) => [ -// schemaName, fn.map( -// schemaDeps, -// ({ type, term }, depName) => makeImport(`${depName}`, { -// type: { -// named: [...type.named.values()].sort(importComparator), -// namespace: [...type.namespace.values()].sort(stringComparator), -// }, -// term: { -// named: [...term.named.values()].sort(importComparator), -// namespace: [...term.namespace.values()].sort(stringComparator), -// }, -// }) -// ) -// ] satisfies [any, any] -// ) -// .reduce( -// (acc, [k, v]) => (acc[k] = Object.values(v).filter((_) => _.length > 0).map((_) => _.join('\n')), acc), -// {} as Record -// ) -// } +// let getDependenciesFromImportsBySchemaName = (extensionsBySchemaName: ExtensionsBySchemaName) => { +// let xs = Object.values(extensionsBySchemaName) +// .filter((_) => !!_) +// .flatMap((_) => Object.values(_).filter((_) => !!_)) +// .flatMap((_) => Object.keys(_).filter((_) => _.startsWith('@traversable/'))) -// export function makeImports>>( -// config: Record, -// dependencies: T -// ): { [K in keyof T]: string } -// export function makeImports>>( -// config: Record, -// dependencies: Record -// ): Record { -// return fn.map(makeImportArraysBySchemaName(config, dependencies), (importArray) => importArray.join('\n')) +// return Array.from(new Set(xs)) // } diff --git a/packages/schema-generator/src/parser.ts b/packages/schema-generator/src/parser.ts index 091b1871..528e4780 100644 --- a/packages/schema-generator/src/parser.ts +++ b/packages/schema-generator/src/parser.ts @@ -7,7 +7,7 @@ import { fn } from '@traversable/registry' export type ParsedSourceFile = { imports: Record body: string - extension?: { type?: string, term?: string } + // extension?: { type?: string, term?: string } } let typesMarker = '//<%= types %>' as const @@ -75,30 +75,57 @@ export function parseFile(sourceFilePath: string): ParsedSourceFile { let isExportedVariable = (node: ts.Node): node is ts.VariableStatement => ts.isVariableStatement(node) && !!node.modifiers?.some((_) => _.kind === ts.SyntaxKind.ExportKeyword) +let isExtensionTerm = (node: ts.Node): node is ts.VariableStatement => + ts.isVariableStatement(node) + && !!node.declarationList.declarations.find((declaration) => declaration.name.getText() === 'extension') + +let isExtensionInterface = (node: ts.Node): node is ts.InterfaceDeclaration => + ts.isInterfaceDeclaration(node) && findIdentifier(node)?.getText() === 'Extension' + +let isExtensionType = (node: ts.Node): node is ts.TypeAliasDeclaration => + ts.isTypeAliasDeclaration(node) && findIdentifier(node)?.getText() === 'Extension' + +let findIdentifier = (node: ts.Node) => node.getChildren().find(ts.isIdentifier) + type ExtensionMetadata = { start: number end: number node: ts.VariableDeclaration } +type ParsedExtensionType = { type: ts.InterfaceDeclaration | ts.TypeAliasDeclaration } +type ParsedExtensionTerm = { term: ts.VariableStatement } +type ParsedExtension = ParsedExtensionType & ParsedExtensionTerm + +export function parseExtensionFile(sourceFilePath: string): { term?: string, type?: string } { + let source = fs.readFileSync(sourceFilePath).toString('utf-8') + let program = createProgram(source) + /* initialize the type checker, otherwise we can't perform a traversal */ + void program.getTypeChecker() + let sourceFile = program.getSourceFiles()[1] + let nodes: Partial = {} + let out: { term?: string, type?: string } = {} + void ts.forEachChild(sourceFile, (node) => { + if (isExtensionType(node)) nodes.type = node + if (isExtensionInterface(node)) nodes.type = node + if (isExtensionTerm(node)) nodes.term = node + }) + if (nodes.type) { + let text = nodes.type.getText() + out.type = text.slice(text.indexOf('{') + 1, text.lastIndexOf('}')) + } + if (nodes.term) { + let text = nodes.term.getText() + out.term = text.slice(text.indexOf('{') + 1, text.lastIndexOf('}')) + } + return out +} + export function parseSourceFile(sourceFile: ts.SourceFile): ParsedSourceFile { let imports: Record = {} let bodyStart: number = 0 - let extensionMetadata: ExtensionMetadata | undefined = undefined void ts.forEachChild(sourceFile, (node) => { - if (isExportedVariable(node)) { - let extension = node.declarationList.declarations - .find((declaration) => declaration.name.getText() === 'extension') - if (extension) { - extensionMetadata = { - start: node.getStart(), - end: node.end, - node: extension, - } - } - } - if (ts.isImportDeclaration(node)) { let importClause = node.importClause if (importClause === undefined) return void 0 @@ -125,69 +152,50 @@ export function parseSourceFile(sourceFile: ts.SourceFile): ParsedSourceFile { }) let content = sourceFile.getFullText() - let body: string - let extension: { type?: string, term?: string } | undefined = undefined - let ext: ExtensionMetadata = extensionMetadata as never - - if (typeof ext === 'object') { - let raw = content.slice(ext.start, ext.end) - let parsed = parseObjectEntries.run(raw.slice(raw.indexOf('{'))) - if (parsed.success) { - extension = fn.map(Object.fromEntries(parsed.value), removeQuotes) - } + let body: string = content.slice(bodyStart).trim() - body = content.slice(bodyStart, ext.start) + content.slice(ext.end).trim() - } - else { - body = content.slice(bodyStart).trim() - } - - return extension === undefined - ? { imports, body } - : { imports, body, extension } + return { imports, body } } -let isQuoted = (text: string) => - (text.startsWith('"') && text.endsWith('"')) - || (text.startsWith('`') && text.endsWith('`')) - || (text.startsWith(`'`) && text.endsWith(`'`)) +let parseTypeMarker = P.seq(P.char('{'), P.spaces, P.string(typesMarker), P.spaces, P.char('}')) +let parseTermMarker = P.seq(P.char('{'), P.spaces, P.string(termsMarker), P.spaces, P.char('}')) -let removeQuotes = (text: string) => isQuoted(text) ? text.slice(1, -1) : text -let parseTypesMarker = P.seq(P.spaces, P.string(typesMarker)).map(([ws, marker]) => [ws?.join('') ?? '', marker] satisfies [any, any]) -let parseTermsMarker = P.seq(P.spaces, P.string(termsMarker)).map(([ws, marker]) => [ws?.join('') ?? '', marker] satisfies [any, any]) +export function replaceExtensions(source: string, extension: { term?: string, type?: string }) { + let typeMarker = parseTypeMarker.find(source) + let termMarker = parseTermMarker.find(source) -export function replaceExtensions(source: string, extensions: { term?: string, type?: string }[]) { - let parsedTypesMarker = parseTypesMarker.find(source)?.result - let parsedTermsMarker = parseTermsMarker.find(source)?.result + if (typeMarker === void 0 || termMarker === void 0) throw Error('missing marker') + if (extension.type === void 0 || extension.term === void 0) throw Error('missing parsed extension') + if (!typeMarker.result.success || !termMarker.result.success) throw Error('marker parse failed') + if (!typeMarker.result.value[1] || !termMarker.result.value[1]) throw Error('unknown error') - let types = !parsedTypesMarker?.success ? null : { - start: parsedTypesMarker.index - parsedTypesMarker.value[1].length, - end: parsedTypesMarker.index, - indentation: Math.max(parsedTypesMarker.value[0].length - 1, 0), - } + let typeIndent = ' '.repeat(Math.max(typeMarker.result.value[1].length - 1, 0)) + // TODO: figure out why you need -3 here + let termIndent = ' '.repeat(Math.max(termMarker.result.value[1].length - 3, 0)) - let terms = !parsedTermsMarker?.success ? null : { - start: parsedTermsMarker.index - parsedTermsMarker.value[1].length, - end: parsedTermsMarker.index, - indentation: Math.max(parsedTermsMarker.value[0].length - 1, 0), + if (typeMarker.index < termMarker.index) { + let out = '' + + source.slice(0, typeMarker.index + 1) + // TODO: figure out why indent isn't working here + + extension.type + // + extension.type.split('\n').map((_) => typeIndent + _).join('\n') + + source.slice(typeMarker.result.index - 1, termMarker.index + 1) + + extension.term.split('\n').map((_) => termIndent + _).join('\n') + // + extension.term + + source.slice(termMarker.result.index - 1) + console.log('out', out) + return out + } else { + let out = '' + + source.slice(0, termMarker.index + 1) + + extension.term.split('\n').map((_) => termIndent + _).join('\r') + + source.slice(termMarker.result.index - 1, typeMarker.index + 1) + + extension.type.split('\n').map((_) => typeIndent + _).join('\n') + + source.slice(typeMarker.result.index - 1) + console.log('out', out) + return out } - - if (!types) throw new Error('expected types') - if (!terms) throw new Error('expected terms') - - if (types.start < terms.start) return '' - + source.slice(0, types.start) - + extensions.map(({ type }, ix) => ix === 0 ? removeQuotes(type ?? '') : ' '.repeat(types.indentation) + removeQuotes(type ?? '')).join('\n') - + source.slice(types.end, terms.start) - + extensions.map(({ term }, ix) => ix === 0 ? removeQuotes(term ?? '') : ' '.repeat(terms.indentation) + removeQuotes(term ?? '')).join(',\n') - + source.slice(terms.end) - else return '' - + source.slice(0, terms.start) - + extensions.map(({ term }, ix) => ix === 0 ? removeQuotes(term ?? '') : ' '.repeat(terms.indentation) + removeQuotes(term ?? '')).join(',\n') - + source.slice(terms.end, types.start) - + extensions.map(({ type }, ix) => ix === 0 ? removeQuotes(type ?? '') : ' '.repeat(types.indentation) + removeQuotes(type ?? '')).join('\n') - + source.slice(types.end) } export function createProgram(source: string): ts.Program { @@ -220,3 +228,55 @@ export function createProgram(source: string): ts.Program { writeFile: () => { throw Error('unimplemented') }, }) } + +// console.log('\n\nparsedTermsMarker', source.slice(parsedTermsMarker)) + +// let terms = !parsedTermsMarker?.success ? void 0 : { +// start: parsedTermsMarker.index - parsedTermsMarker.value[1].length, +// end: parsedTermsMarker.index, +// indentation: Math.max(parsedTermsMarker.value[0].length - 1, 0), +// } + +// console.log('extension', extension) + +// let types = !parsedTypesMarker?.success ? void 0 : { +// start: parsedTypesMarker.index - parsedTypesMarker.value[1].length, +// end: parsedTypesMarker.index, +// indentation: Math.max(parsedTypesMarker.value[0].length - 1, 0), +// } + +// let terms = !parsedTermsMarker?.success ? void 0 : { +// start: parsedTermsMarker.index - parsedTermsMarker.value[1].length, +// end: parsedTermsMarker.index, +// indentation: Math.max(parsedTermsMarker.value[0].length - 1, 0), +// } + +// if (types === void 0) throw new Error('expected types') +// if (terms === void 0) { +// console.log(source) +// throw new Error('expected terms') +// } + +// if (types.start < terms.start) return '' +// + source.slice(0, types.start) +// + extensions.map(({ type }, ix) => ix === 0 ? removeQuotes(type ?? '') : ' '.repeat(types.indentation) + removeQuotes(type ?? '')).join('\n') +// + source.slice(types.end, terms.start) +// + extensions.map(({ term }, ix) => ix === 0 ? removeQuotes(term ?? '') : ' '.repeat(terms.indentation) + removeQuotes(term ?? '')).join(',\n') +// + source.slice(terms.end) +// else return '' +// + source.slice(0, terms.start) +// + extensions.map(({ term }, ix) => ix === 0 ? removeQuotes(term ?? '') : ' '.repeat(terms.indentation) + removeQuotes(term ?? '')).join(',\n') +// + source.slice(terms.end, types.start) +// + extensions.map(({ type }, ix) => ix === 0 ? removeQuotes(type ?? '') : ' '.repeat(types.indentation) + removeQuotes(type ?? '')).join('\n') +// + source.slice(types.end) + + +// let removeQuotes = (text: string) => isQuoted(text) ? text.slice(1, -1) : text + +// let parseTypesMarker = P.seq(P.spaces, P.string(typesMarker)).map(([ws, marker]) => [ws?.join('') ?? '', marker] satisfies [any, any]) +// let parseTermsMarker = P.seq(P.spaces, P.string(termsMarker)).map(([ws, marker]) => [ws?.join('') ?? '', marker] satisfies [any, any]) + +// let isQuoted = (text: string) => +// (text.startsWith('"') && text.endsWith('"')) +// || (text.startsWith('`') && text.endsWith('`')) +// || (text.startsWith(`'`) && text.endsWith(`'`)) diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index d518a4b0..ec7f9a20 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -3,12 +3,14 @@ import * as path from 'node:path' import * as fs from 'node:fs' import { + deduplicateImports, makeImport, makeImports, parseFile, replaceExtensions, writeSchemas, } from '@traversable/schema-generator' +import { fn } from '@traversable/registry' let DIR_PATH = path.join(path.resolve(), 'packages', 'schema-generator', 'test') let DATA_PATH = path.join(DIR_PATH, 'test-data') @@ -18,11 +20,20 @@ let PATH = { sources: { array: { core: path.join(DATA_PATH, 'array', 'core.ts'), + extension: path.join(DATA_PATH, 'array', 'extension.ts'), equals: path.join(DATA_PATH, 'array', 'equals.ts'), toJsonSchema: path.join(DATA_PATH, 'array', 'toJsonSchema.ts'), toString: path.join(DATA_PATH, 'array', 'toString.ts'), validate: path.join(DATA_PATH, 'array', 'validate.ts'), }, + string: { + core: path.join(DATA_PATH, 'string', 'core.ts'), + extension: path.join(DATA_PATH, 'string', 'extension.ts'), + equals: path.join(DATA_PATH, 'string', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'string', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'string', 'toString.ts'), + validate: path.join(DATA_PATH, 'string', 'validate.ts'), + }, }, targets: { array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), @@ -49,6 +60,161 @@ vi.describe('〖️⛳️〗‹‹‹ ❲make❳', () => { `) }) + vi.it('〖️⛳️〗› ❲deduplicateImports❳', () => { + vi.expect(deduplicateImports({ + array: { + core: { + "@traversable/registry": { + type: { + named: ['PickIfDefined as Pick'], + namespace: 'T' + }, + term: { + named: ['pick'] + } + }, + "@traversable/schema": { + type: { + named: ['t', 'TypeGuard as Guard', 'Bounds'], + }, + term: { + named: [], + }, + } + }, + toString: { + "@traversable/registry": { + type: { + named: ['Record', 'PickIfDefined'], + }, + term: { + named: ['omit'], + namespace: 'T', + } + }, + "@traversable/schema": { + type: { + named: [], + }, + term: { + named: ['t'] + } + } + } + }, + string: { + core: { + "@traversable/registry": { + type: { + named: ['PickIfDefined as Pick'], + namespace: 'T' + }, + term: { + named: ['pick'] + } + }, + }, + equals: {}, + toJsonSchema: { + '@traversable/schema-to-json-schema': { + type: { + named: [], + namespace: 'JsonSchema', + }, + term: { + named: ['stringToJsonSchema'] + } + } + } + } + })).toMatchInlineSnapshot(` + { + "array": { + "@traversable/registry": { + "term": { + "named": Set { + "pick", + "omit", + }, + "namespace": Set { + "T", + }, + }, + "type": { + "named": Set { + "PickIfDefined as Pick", + "Record", + "PickIfDefined", + }, + "namespace": Set {}, + }, + }, + "@traversable/schema": { + "term": { + "named": Set { + "t", + }, + "namespace": Set {}, + }, + "type": { + "named": Set { + "TypeGuard as Guard", + "Bounds", + }, + "namespace": Set {}, + }, + }, + }, + "string": { + "@traversable/registry": { + "term": { + "named": Set { + "pick", + }, + "namespace": Set {}, + }, + "type": { + "named": Set { + "PickIfDefined as Pick", + }, + "namespace": Set { + "T", + }, + }, + }, + "@traversable/schema-to-json-schema": { + "term": { + "named": Set { + "stringToJsonSchema", + }, + "namespace": Set {}, + }, + "type": { + "named": Set {}, + "namespace": Set { + "JsonSchema", + }, + }, + }, + }, + } + `) + }) + + + vi.it('〖️⛳️〗› ❲makeImports❳', () => { + // let src = { + // core: parseFile(PATH.sources.string.core), + // extension: parseFile(PATH.sources.string.extension), + // equals: parseFile(PATH.sources.string.equals), + // toJsonSchema: parseFile(PATH.sources.string.toJsonSchema), + // toString: parseFile(PATH.sources.string.toString), + // validate: parseFile(PATH.sources.string.validate), + // } + + // makeImports({ string: fn.map(src, (file) => file.imports) }) + }) + // vi.it('〖️⛳️〗› ❲makeImports❳', () => { // vi.expect(makeImports({ equals: true, toJsonSchema: true, toString: true, validate: true }, dependencies)).toMatchInlineSnapshot(` // { @@ -76,11 +242,13 @@ vi.describe('〖️⛳️〗‹‹‹ ❲make❳', () => { vi.describe('〖️⛳️〗‹‹‹ ❲parse❳', () => { vi.it('〖️⛳️〗› ❲getFileImports❳', () => { if (!fs.existsSync(DIR_PATH)) fs.mkdirSync(DIR_PATH) - if (!fs.existsSync(DATA_PATH)) { - fs.mkdirSync(DATA_PATH) - } - + if (!fs.existsSync(DATA_PATH)) fs.mkdirSync(DATA_PATH) if (!fs.existsSync(PATH.__generated__)) fs.mkdirSync(PATH.__generated__) - writeSchemas({ equals: true, toJsonSchema: true, toString: true, validate: true }, PATH.sources, PATH.targets) + + writeSchemas( + PATH.sources, + PATH.targets, + ) }) -}) \ No newline at end of file +}) + diff --git a/packages/schema-generator/test/test-data/array/core.ts b/packages/schema-generator/test/test-data/array/core.ts index 29c8439f..0527686f 100644 --- a/packages/schema-generator/test/test-data/array/core.ts +++ b/packages/schema-generator/test/test-data/array/core.ts @@ -1,3 +1,4 @@ +import type { Unknown } from '@traversable/registry' import { Array_isArray, Math_max, @@ -6,9 +7,20 @@ import { URI, } from '@traversable/registry' + import type { Bounds } from '@traversable/schema' import { t, __carryover as carryover, __within as within } from '@traversable/schema' +let bindUserDefinitions = (userDefinitions: Record) => { + for (let k in userDefinitions) { + userDefinitions[k] = + typeof userDefinitions[k] === 'function' + ? userDefinitions[k](self) + : userDefinitions[k] + } + return userDefinitions +} + export interface array extends array.core { //<%= types %> } @@ -21,11 +33,11 @@ export namespace array { export function def(x: S, prev?: array): array export function def(x: S, prev?: unknown): array export function def(x: S, prev?: array): array - export function def(x: S, prev?: unknown): array { + export function def(x: S, prev?: unknown): {} { let proto = { tag: URI.array, } as { tag: URI.array, _type: S['_type' & keyof S][] } - let userDefinitions = { + let userDefinitions: Record = { //<%= terms %> } const arrayPredicate = t.isPredicate(x) ? array$(x) : Array_isArray @@ -56,13 +68,13 @@ export namespace array { self.def = x if (t.has('minLength', t.integer)(prev)) self.minLength = prev.minLength if (t.has('maxLength', t.integer)(prev)) self.maxLength = prev.maxLength - return Object.assign(self, { ...proto, ...userDefinitions }) + return Object.assign(self, { ...proto, ...bindUserDefinitions(userDefinitions) }) } } export declare namespace array { interface core { - (u: unknown): u is this['_type'] + (u: S[] | Unknown): u is this['_type'] tag: URI.array def: S _type: S['_type' & keyof S][] diff --git a/packages/schema-generator/test/test-data/array/equals.ts b/packages/schema-generator/test/test-data/array/equals.ts index 75f8d595..39e977d8 100644 --- a/packages/schema-generator/test/test-data/array/equals.ts +++ b/packages/schema-generator/test/test-data/array/equals.ts @@ -1,17 +1,11 @@ -import * as T from '@traversable/registry' +import type * as T from '@traversable/registry' import { has, Array_isArray, Object_is } from '@traversable/registry' -import { t } from '@traversable/schema' +import type { t } from '@traversable/schema' -export const extension = { - type: `equals: equals`, - term: 'equals: equals(x)', -} as const - -export type equals = never | T.Equal -/// -export function equals(schema: S): equals -export function equals(schema: S): equals -export function equals({ def }: { def: unknown }): T.Equal { +export type equals = never | T.Equal +export function equals>(arraySchema: S): equals +export function equals(arraySchema: S): equals +export function equals({ def: { def } }: { def: { def: unknown } }): T.Equal { let equals = has('equals', (x): x is T.Equal => typeof x === 'function')(def) ? def.equals : Object_is return (l, r) => { if (Object_is(l, r)) return true @@ -24,4 +18,4 @@ export function equals({ def }: { def: unknown }): T.Equal { return true } else return false } -} \ No newline at end of file +} diff --git a/packages/schema-generator/test/test-data/array/extension.ts b/packages/schema-generator/test/test-data/array/extension.ts new file mode 100644 index 00000000..de3f543b --- /dev/null +++ b/packages/schema-generator/test/test-data/array/extension.ts @@ -0,0 +1,18 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export let extension = { + toString, + equals, + toJsonSchema, + validate, +} + +export interface Extension { + toString(): toString + equals: equals + toJsonSchema(): toJsonSchema + validate: validate +} diff --git a/packages/schema-generator/test/test-data/array/toJsonSchema.ts b/packages/schema-generator/test/test-data/array/toJsonSchema.ts index 2389f0af..2d20d7ac 100644 --- a/packages/schema-generator/test/test-data/array/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/array/toJsonSchema.ts @@ -3,17 +3,12 @@ import type * as T from '@traversable/registry' import { has } from '@traversable/registry' import type { SizeBounds } from '@traversable/schema-to-json-schema' -export const extension = { - type: 'toJsonSchema(): toJsonSchema', - term: 'toJsonSchema: toJsonSchema(self)', -} - -export type toJsonSchema = never | T.Force< - & { type: 'array', items: T.Returns } - & T.PickIfDefined +export type toJsonSchema = never | T.Force< + & { type: 'array', items: T.Returns } + & T.PickIfDefined > -export function toJsonSchema>(self: T): () => toJsonSchema -export function toJsonSchema(self: T): () => toJsonSchema +export function toJsonSchema>(arraySchema: T): () => toJsonSchema +export function toJsonSchema(arraySchema: T): () => toJsonSchema export function toJsonSchema( { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, ): () => { diff --git a/packages/schema-generator/test/test-data/array/toString.ts b/packages/schema-generator/test/test-data/array/toString.ts index 1e79ba40..7581c0f9 100644 --- a/packages/schema-generator/test/test-data/array/toString.ts +++ b/packages/schema-generator/test/test-data/array/toString.ts @@ -1,14 +1,10 @@ -export const extension = { - type: 'toString(): toString', - term: 'toString: toString(x)', -} +import type { t } from '@traversable/schema' /* @ts-expect-error */ -export type toString = never | `(${ReturnType})[]` -/// -export function toString(x: { def: S }): () => toString -export function toString(x: S): () => toString -export function toString({ def }: { def: unknown }) { +export type toString = never | `(${ReturnType})[]` +export function toString>(arraySchema: S): () => toString +export function toString(arraySchema: S): () => toString +export function toString({ def: { def } }: { def: { def: unknown } }) { return () => { let body = ( !!def diff --git a/packages/schema-generator/test/test-data/array/validate.ts b/packages/schema-generator/test/test-data/array/validate.ts index 8955999e..3ce76660 100644 --- a/packages/schema-generator/test/test-data/array/validate.ts +++ b/packages/schema-generator/test/test-data/array/validate.ts @@ -3,24 +3,18 @@ import type { ValidationError, ValidationFn } from '@traversable/derive-validato import { URI } from '@traversable/registry' import { Errors, NullaryErrors } from '@traversable/derive-validators' -export const extension = { - type: `validate(u: this['_type'] | T.Unknown): true | ValidationError[]`, - term: `validate: validate(x)`, -} as const - export type validate = never | ValidationFn - -export function validate( - itemsSchema: S, +export function validate>( + arraySchema: S, bounds?: { minLength?: number, maxLength?: number } ): validate export function validate( - itemsSchema: S, + arraySchema: S, bounds?: { minLength?: number, maxLength?: number } ): validate // export function validate( - { def }: { def: unknown }, + { def: { def } }: { def: { def: unknown } }, { minLength, maxLength }: { minLength?: number, maxLength?: number } = {} ): ValidationFn { let validate = (( diff --git a/packages/schema-generator/test/test-data/object/core.ts b/packages/schema-generator/test/test-data/object/core.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/object/equals.ts b/packages/schema-generator/test/test-data/object/equals.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/object/extension.ts b/packages/schema-generator/test/test-data/object/extension.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/object/toJsonSchema.ts b/packages/schema-generator/test/test-data/object/toJsonSchema.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/object/toString.ts b/packages/schema-generator/test/test-data/object/toString.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/object/validate.ts b/packages/schema-generator/test/test-data/object/validate.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/string/core.ts b/packages/schema-generator/test/test-data/string/core.ts new file mode 100644 index 00000000..eae438f6 --- /dev/null +++ b/packages/schema-generator/test/test-data/string/core.ts @@ -0,0 +1,84 @@ +import type { Unknown } from '@traversable/registry' +import { Math_min, Math_max, Object_assign, URI } from '@traversable/registry' + +import type { Bounds } from '@traversable/schema' +import { __carryover as carryover, __within as within } from '@traversable/schema' + +interface string_ extends string_.core { + //<%= types %> +} + +export let userDefinitions = { + //<%= terms %> +} + +export { string_ as string } +const string_ = Object_assign( + function StringSchema(src: unknown) { return typeof src === 'string' }, + userDefinitions, +) as string_ + +string_.tag = URI.string +string_.def = '' +string_.min = function stringMinLength(minLength) { + return Object_assign( + boundedString({ gte: minLength }, carryover(this, 'minLength')), + { minLength }, + ) +} +string_.max = function stringMaxLength(maxLength) { + return Object_assign( + boundedString({ lte: maxLength }, carryover(this, 'maxLength')), + { maxLength }, + ) +} +string_.between = function stringBetween( + min, + max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max)) { + return Object_assign( + boundedString({ gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) +} + +declare namespace string_ { + interface core extends string_.methods { + (u: string | Unknown): u is string + _type: string + tag: URI.string + def: this['_type'] + } + interface methods { + minLength?: number + maxLength?: number + min(minLength: Min): string_.Min + max(maxLength: Max): string_.Max + between(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maxLength: number }] + ? string_.between<[min: Min, max: Self['maxLength']]> + : string_.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? string_.between<[min: Self['minLength'], max: Max]> + : string_.max + ; + interface min extends string_ { minLength: Min } + interface max extends string_ { maxLength: Max } + interface between extends string_ { + minLength: Bounds[0] + maxLength: Bounds[1] + } +} + +function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedStringSchema(u: unknown) { + return string_(u) && within(bounds)(u.length) + }, carry, string_) +} diff --git a/packages/schema-generator/test/test-data/string/equals.ts b/packages/schema-generator/test/test-data/string/equals.ts new file mode 100644 index 00000000..758a2ab6 --- /dev/null +++ b/packages/schema-generator/test/test-data/string/equals.ts @@ -0,0 +1 @@ +export function equals(left: string, right: string): boolean { return left === right } diff --git a/packages/schema-generator/test/test-data/string/extension.ts b/packages/schema-generator/test/test-data/string/extension.ts new file mode 100644 index 00000000..80eff013 --- /dev/null +++ b/packages/schema-generator/test/test-data/string/extension.ts @@ -0,0 +1,20 @@ +import type { t } from '@traversable/schema' +import type { Equal, Unknown } from '@traversable/registry' +import type { ValidationError } from '@traversable/derive-validators' + +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' + +export let extension = { + toString(): 'string' { return 'string' }, + equals(left: string, right: string): boolean { return left === right }, + toJsonSchema(this: S): toJsonSchema { return toJsonSchema(this) }, + validate, +} + +export interface Extension { + toString(): 'string' + equals: Equal + toJsonSchema(): toJsonSchema + validate(u: string | Unknown): true | ValidationError[] +} diff --git a/packages/schema-generator/test/test-data/string/toJsonSchema.ts b/packages/schema-generator/test/test-data/string/toJsonSchema.ts new file mode 100644 index 00000000..f043be3c --- /dev/null +++ b/packages/schema-generator/test/test-data/string/toJsonSchema.ts @@ -0,0 +1,16 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '@traversable/schema' +import { has } from '@traversable/registry' +import type { SizeBounds } from '@traversable/schema-to-json-schema' + +export type toJsonSchema = Force<{ type: 'string' } & PickIfDefined> + +export function toJsonSchema(schema: t.string): toJsonSchema +export function toJsonSchema(schema: t.string): { type: 'string' } & Partial { + const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null + const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null + let out: { type: 'string' } & Partial = { type: 'string' } + minLength !== null && void (out.minLength = minLength) + maxLength !== null && void (out.maxLength = maxLength) + return out +} diff --git a/packages/schema-generator/test/test-data/string/toString.ts b/packages/schema-generator/test/test-data/string/toString.ts new file mode 100644 index 00000000..f8d6f588 --- /dev/null +++ b/packages/schema-generator/test/test-data/string/toString.ts @@ -0,0 +1 @@ +export function toString(): 'string' { return 'string' } diff --git a/packages/schema-generator/test/test-data/string/validate.ts b/packages/schema-generator/test/test-data/string/validate.ts new file mode 100644 index 00000000..13c60726 --- /dev/null +++ b/packages/schema-generator/test/test-data/string/validate.ts @@ -0,0 +1,6 @@ +import type { ValidationError } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export function validate(this: (u: any) => boolean, u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return this(u) || [NullaryErrors.string(u, path)] +} diff --git a/packages/schema/src/namespace.ts b/packages/schema/src/namespace.ts index e0bc9849..baa2d4aa 100644 --- a/packages/schema/src/namespace.ts +++ b/packages/schema/src/namespace.ts @@ -35,6 +35,7 @@ export { unfold, tags, } from './schema.js' +export type { Typeguard } from './types.js' export { key, has } from './has.js' From bde57db217420d34a3ef5b31a20868e40a49e598 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 9 Apr 2025 16:18:28 -0500 Subject: [PATCH 13/45] feat(generator): adds object generator --- packages/derive-equals/src/install.ts | 5 - packages/derive-validators/src/exports.ts | 1 + packages/registry/src/bindUserDefinitions.ts | 9 ++ packages/registry/src/exports.ts | 1 + packages/registry/src/globalThis.ts | 46 +++++-- packages/registry/src/merge.ts | 7 +- packages/registry/src/satisfies.ts | 22 +++- packages/registry/test/globalThis.test.ts | 26 ++++ packages/registry/test/merge.test.ts | 8 +- .../schema-generator/test/imports.test.ts | 9 ++ .../test/test-data/array/core.ts | 33 ++--- .../test/test-data/object/core.ts | 81 ++++++++++++ .../test/test-data/object/equals.ts | 32 +++++ .../test/test-data/object/extension.ts | 18 +++ .../test/test-data/object/toJsonSchema.ts | 28 +++++ .../test/test-data/object/toString.ts | 36 ++++++ .../test/test-data/object/validate.ts | 118 ++++++++++++++++++ packages/schema-to-json-schema/src/exports.ts | 2 + 18 files changed, 432 insertions(+), 50 deletions(-) create mode 100644 packages/registry/src/bindUserDefinitions.ts create mode 100644 packages/registry/test/globalThis.test.ts diff --git a/packages/derive-equals/src/install.ts b/packages/derive-equals/src/install.ts index a4fc0eec..b844f3c3 100644 --- a/packages/derive-equals/src/install.ts +++ b/packages/derive-equals/src/install.ts @@ -36,11 +36,6 @@ const hasEquals : (u: unknown) => u is { equals: Equal } = has('equals', (u): u is Equal => typeof u === 'function' && u.length === 2) -/** @internal */ -const getEquals - : (u: unknown) => Equal - = (u) => hasEquals(u) ? u.equals : Object_is - /** @internal */ const Object_assign = globalThis.Object.assign diff --git a/packages/derive-validators/src/exports.ts b/packages/derive-validators/src/exports.ts index e79a1fda..a412e6a1 100644 --- a/packages/derive-validators/src/exports.ts +++ b/packages/derive-validators/src/exports.ts @@ -15,6 +15,7 @@ export type { export { NULLARY as NullaryErrors, + UNARY as UnaryErrors, ERROR as Errors, ErrorType, dataPath as dataPathFromSchemaPath, diff --git a/packages/registry/src/bindUserDefinitions.ts b/packages/registry/src/bindUserDefinitions.ts new file mode 100644 index 00000000..73a8c4e1 --- /dev/null +++ b/packages/registry/src/bindUserDefinitions.ts @@ -0,0 +1,9 @@ +export let bindUserDefinitions = (schema: {}, userDefinitions: Record) => { + for (let k in userDefinitions) { + userDefinitions[k] = + typeof userDefinitions[k] === 'function' + ? userDefinitions[k](schema) + : userDefinitions[k] + } + return userDefinitions +} diff --git a/packages/registry/src/exports.ts b/packages/registry/src/exports.ts index 08fe08c8..478ab08a 100644 --- a/packages/registry/src/exports.ts +++ b/packages/registry/src/exports.ts @@ -43,6 +43,7 @@ export { } from './has.js' export { unsafeCompact } from './compact.js' +export { bindUserDefinitions } from './bindUserDefinitions.js' export { objectFromKeys, diff --git a/packages/registry/src/globalThis.ts b/packages/registry/src/globalThis.ts index 9a33fcbb..02b745a4 100644 --- a/packages/registry/src/globalThis.ts +++ b/packages/registry/src/globalThis.ts @@ -1,3 +1,17 @@ +import type { + EmptyObject, + FiniteArray, + FiniteObject, + NonFiniteArray, + NonFiniteRecord, + StringIndexed, + NumberIndexed, + PlainObject, + SymbolIndexed, + ObjectAsTypeAlias, + MixedNonFinite +} from "@traversable/registry/satisfies" + export const Array_isArray : (u: unknown) => u is readonly T[] = globalThis.Array.isArray @@ -9,16 +23,34 @@ export const Number_isSafeInteger = globalThis.Number.isSafeInteger export const Number_isInteger = globalThis.Number.isInteger export const Object_assign = globalThis.Object.assign - -export type Object_entries = K extends K ? [k: K, v: T[K]][] : never -export const Object_entries - : (x: T) => Object_entries - = globalThis.Object.entries - export const Object_is = globalThis.Object.is +export const Object_values = globalThis.Object.values + +export const Object_hasOwn + : (u: unknown, k: K) => u is Record + = (u, k): u is never => !!u && typeof u === 'object' && globalThis.Object.prototype.hasOwnProperty.call(u, k) export const Object_keys : (x: T) => (K)[] = globalThis.Object.keys -export const Object_values = globalThis.Object.values +export type Object_entries = never | (K extends K ? [k: K, v: T[K & keyof T]] : never)[] +export const Object_entries: { + >(x: T): MixedNonFiniteEntries + >(x: T): [k: string, v?: unknown][] + >(x: T): [k: string, v: unknown][] + , K extends Extract>(x: T): Object_entries + , K extends keyof T>(x: T): Object_entries + >(x: T): [k: keyof T, v: T[keyof T]][] + >(x: T): [k: keyof T, v: T[keyof T]][] + , K extends Extract>(x: T): [k: `${number}`, v: T[number]][] + >(x: T): [k: string | number, v: T[keyof T]][] + >(x: T): [k: symbol, v: T[keyof T]][] + >(x: T): [k: string, v: T[keyof T]][] +} = globalThis.Object.entries as never + +export type MixedNonFiniteEntries< + T, + O = [T] extends [T[number & keyof T][] & infer U] ? U : never, + A = [T] extends [O & infer U] ? U : never +> = never | ([k: keyof O, v: O[keyof O]] | [k: `${number}`, v: A[number & keyof A]])[] diff --git a/packages/registry/src/merge.ts b/packages/registry/src/merge.ts index 19095b21..f309957b 100644 --- a/packages/registry/src/merge.ts +++ b/packages/registry/src/merge.ts @@ -68,7 +68,9 @@ export function merge, R extends FiniteObject>(l export function merge, R extends NonFiniteObject>(l: L, r: R): Force export function merge, R extends NonFiniteArray>(l: L, r: R): Force export function merge, R extends NonFiniteObject>(l: L, r: R): Force> + export function merge, R extends NonFiniteArray>(l: L, r: R): Force> + export function merge, R extends NonFiniteObject>(l: L, r: R): Force export function merge(l: {}, r: {}): unknown { if (Array.isArray(l) && Array.isArray(r)) return [...l, ...r] @@ -77,8 +79,3 @@ export function merge(l: {}, r: {}): unknown { return Object.assign(l_, r) } } - - -// type FiniteArrayToFiniteObject = never | { [I in keyof T as I extends `${number}` ? I : never]: T[I] } -// type NonFiniteArrayToFiniteObject = never | { [x: number]: T[number] } -// type MixedToObject = [T] extends [Record & infer U] ? U : [fail: Record] \ No newline at end of file diff --git a/packages/registry/src/satisfies.ts b/packages/registry/src/satisfies.ts index e035cc31..2f386cdb 100644 --- a/packages/registry/src/satisfies.ts +++ b/packages/registry/src/satisfies.ts @@ -51,19 +51,31 @@ export type NonUnion< > = _ extends _ ? [T] extends [_] ? _ : never : never export type NonFiniteArray - = [T] extends [readonly any[]] + = [T] extends [readonly unknown[]] ? number extends T['length'] ? readonly unknown[] : never : never export type NonFiniteObject - = string extends keyof T ? Record - : number extends keyof T ? Record - : never + = string extends keyof T ? { [x: string]: unknown } + : number extends keyof T ? { [x: number]: unknown } : never + +export type NonFiniteRecord = number extends keyof T ? never : symbol extends keyof T ? never : string extends keyof T ? { [x: string]: unknown } : never + +export type MixedNonFinite = [T] extends [readonly unknown[]] ? [T] extends [Record] ? {} : never : never +export type FiniteArrayNonFiniteObject = [FiniteArray] extends [never] ? never : [NonFiniteObject] extends [never] ? never : {}; + +/** @internal */ +interface EmptyStringInterface { [x: string]: unknown } export type FiniteArray = [T] extends [readonly any[]] ? number extends T['length'] ? never : Mut : never export type FiniteObject = [T] extends [Record] ? string extends keyof T ? never : number extends keyof T ? never : Mut : never - +export type PlainObject = [keyof T] extends [never] ? string extends T ? never : object : never +export type EmptyObject = [keyof T] extends [never] ? string extends T ? {} : never : never +export type SymbolIndexed = string extends keyof T ? never : number extends keyof T ? never : symbol extends keyof T ? { [x: symbol]: unknown } : never +export type ObjectAsTypeAlias = string extends keyof T ? EmptyStringInterface : never +export type StringIndexed = number extends keyof T ? never : symbol extends keyof T ? never : string extends keyof T ? { [x: string]: unknown } : never +export type NumberIndexed = string extends keyof T ? never : symbol extends keyof T ? never : number extends keyof T ? { [x: number]: unknown } : never export type FiniteIndex = string extends keyof T ? never : Record export type FiniteIndices = [T] extends [readonly any[]] ? number extends T['length'] ? never : readonly unknown[] : never diff --git a/packages/registry/test/globalThis.test.ts b/packages/registry/test/globalThis.test.ts new file mode 100644 index 00000000..31bac57b --- /dev/null +++ b/packages/registry/test/globalThis.test.ts @@ -0,0 +1,26 @@ +import * as vi from 'vitest' + +import { Object_entries } from '@traversable/registry' + +let x: any = {} + +vi.describe('〖⛳️〗‹‹‹ ❲@traverable/registry❳: globalThis', () => { + vi.it('〖⛳️〗‹‹‹ ❲Object_entries❳: typelevel tests', () => { + let unknownEntry = [String(), Array.of()[0]] as const satisfies [any, any] + let optionalEntry: [string, _?: unknown] = unknownEntry + + vi.expectTypeOf(Object_entries(x as object)).toEqualTypeOf(Array.of(unknownEntry)) + vi.expectTypeOf(Object_entries({})).toEqualTypeOf(Array.of(optionalEntry)) + vi.expectTypeOf(Object_entries({ [String()]: 1 as const })).toEqualTypeOf(Array.of([String(), 1 as const] satisfies [any, any])) + vi.expectTypeOf(Object_entries({ a: 3, b: 4 })).toEqualTypeOf(Array.of<[k: "a", v: 3] | [k: "b", v: 4]>()) + vi.expectTypeOf(Object_entries([[1, 2], [3, 4]])).toEqualTypeOf(Array.of<[k: "0", v: [1, 2]] | [k: "1", v: [3, 4]]>()) + vi.expectTypeOf(Object_entries(Array.of(4 as const))).toEqualTypeOf(Array.of<[k: `${number}`, v: 4]>()) + vi.expectTypeOf(Object_entries({ [String()]: 5 as const })).toEqualTypeOf(Array.of<[k: string, v: 5]>()) + vi.expectTypeOf(Object_entries({ [Number()]: 6 as const })).toEqualTypeOf(Array.of<[k: string | number, v: 6]>()) + vi.expectTypeOf(Object_entries({ [Symbol()]: 7 })).toEqualTypeOf(Array.of<[k: symbol, v: 7]>()) + vi.expectTypeOf(Object_entries(Object.assign({} as Record, Array.of(Boolean())))) + .toEqualTypeOf(Array.of<[k: string, v: 9000] | [k: `${number}`, v: boolean]>()) + vi.expectTypeOf(Object_entries(Object.assign([1, 2, 3] as const, { a: 4, b: 5, c: 6 } as const))) + .toEqualTypeOf(Array.of<[k: "0", v: 1] | [k: "1", v: 2] | [k: "2", v: 3]>()) + }) +}) diff --git a/packages/registry/test/merge.test.ts b/packages/registry/test/merge.test.ts index 132baf82..786675d2 100644 --- a/packages/registry/test/merge.test.ts +++ b/packages/registry/test/merge.test.ts @@ -1,6 +1,6 @@ import * as vi from 'vitest' -import { merge, mut } from '@traversable/registry' +import { merge, mut, NonFiniteArray, NonFiniteObject, NonFiniteRecord, NumberIndexed } from '@traversable/registry' vi.describe('〖⛳️〗‹‹‹ ❲@traverable/registry❳: merge', () => { vi.it('〖⛳️〗‹‹‹ ❲merge❳: typelevel tests', () => { @@ -33,11 +33,5 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/registry❳: merge', () => { .toEqualTypeOf(mut({ [Number()]: Boolean() || null }, { [String()]: Number() || String() },)) vi.expectTypeOf(merge(Array(Boolean() || null), { [String() || Number()]: Number() || String() })) .toEqualTypeOf(mut({ [Number()]: Boolean() || null }, { [String()]: Number() || String() },)) - - // vi.expectTypeOf(merge({ [Symbol()]: Number() || String() }, Array(Boolean() || Symbol()))) - // .toEqualTypeOf(mut({ [Symbol()]: Number() || String() }, { [Number()]: Boolean() || Symbol() })) - // vi.expectTypeOf(merge(Array(Boolean() || null), { [Symbol()]: Number() || String() })) - // .toEqualTypeOf(mut({ [Number()]: Boolean() || null }, { [Symbol()]: Number() || String() },)) - }) }) diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index ec7f9a20..db5c6cc0 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -34,9 +34,18 @@ let PATH = { toString: path.join(DATA_PATH, 'string', 'toString.ts'), validate: path.join(DATA_PATH, 'string', 'validate.ts'), }, + object: { + core: path.join(DATA_PATH, 'object', 'core.ts'), + extension: path.join(DATA_PATH, 'object', 'extension.ts'), + equals: path.join(DATA_PATH, 'object', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'object', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'object', 'toString.ts'), + validate: path.join(DATA_PATH, 'object', 'validate.ts'), + }, }, targets: { array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), + object: path.join(DIR_PATH, '__generated__', 'object.gen.ts'), string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), } } diff --git a/packages/schema-generator/test/test-data/array/core.ts b/packages/schema-generator/test/test-data/array/core.ts index 0527686f..7a9ff6a5 100644 --- a/packages/schema-generator/test/test-data/array/core.ts +++ b/packages/schema-generator/test/test-data/array/core.ts @@ -1,6 +1,7 @@ import type { Unknown } from '@traversable/registry' import { Array_isArray, + bindUserDefinitions, Math_max, Math_min, Object_assign, @@ -11,16 +12,6 @@ import { import type { Bounds } from '@traversable/schema' import { t, __carryover as carryover, __within as within } from '@traversable/schema' -let bindUserDefinitions = (userDefinitions: Record) => { - for (let k in userDefinitions) { - userDefinitions[k] = - typeof userDefinitions[k] === 'function' - ? userDefinitions[k](self) - : userDefinitions[k] - } - return userDefinitions -} - export interface array extends array.core { //<%= types %> } @@ -30,31 +21,31 @@ export function array(schema: S): array> export function array(schema: S): array { return array.def(schema) } export namespace array { + export let proto = {} export function def(x: S, prev?: array): array export function def(x: S, prev?: unknown): array export function def(x: S, prev?: array): array export function def(x: S, prev?: unknown): {} { - let proto = { - tag: URI.array, - } as { tag: URI.array, _type: S['_type' & keyof S][] } let userDefinitions: Record = { //<%= terms %> } const arrayPredicate = t.isPredicate(x) ? array$(x) : Array_isArray - function self(src: unknown): src is array['_type'] { return arrayPredicate(src) } - self.min = function arrayMin(minLength: Min) { + function ArraySchema(src: unknown): src is array['_type'] { return arrayPredicate(src) } + ArraySchema.tag = URI.array + ArraySchema.def = x + ArraySchema.min = function arrayMin(minLength: Min) { return Object_assign( boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), { minLength }, ) } - self.max = function arrayMax(maxLength: Max) { + ArraySchema.max = function arrayMax(maxLength: Max) { return Object_assign( boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), { maxLength }, ) } - self.between = function arrayBetween( + ArraySchema.between = function arrayBetween( min: Min, max: Max, minLength = Math_min(min, max), @@ -65,10 +56,10 @@ export namespace array { { minLength, maxLength }, ) } - self.def = x - if (t.has('minLength', t.integer)(prev)) self.minLength = prev.minLength - if (t.has('maxLength', t.integer)(prev)) self.maxLength = prev.maxLength - return Object.assign(self, { ...proto, ...bindUserDefinitions(userDefinitions) }) + if (t.has('minLength', t.integer)(prev)) ArraySchema.minLength = prev.minLength + if (t.has('maxLength', t.integer)(prev)) ArraySchema.maxLength = prev.maxLength + Object_assign(ArraySchema, proto) + return Object_assign(ArraySchema, bindUserDefinitions(ArraySchema, userDefinitions)) } } diff --git a/packages/schema-generator/test/test-data/object/core.ts b/packages/schema-generator/test/test-data/object/core.ts index e69de29b..25e5747e 100644 --- a/packages/schema-generator/test/test-data/object/core.ts +++ b/packages/schema-generator/test/test-data/object/core.ts @@ -0,0 +1,81 @@ +import type { Force } from '@traversable/registry' +import { + Array_isArray, + applyOptions, + bindUserDefinitions, + fn, + Object_assign, + Object_keys, + URI, +} from '@traversable/registry' +import type { SchemaOptions as Options } from '@traversable/schema' +import { t, Predicate } from '@traversable/schema' + +interface object_ extends object_.core { + //<%= types %> +} + +export { object_ as object } +function object_< + S extends { [x: string]: t.Schema }, + T extends { [K in keyof S]: t.Entry } +>(schemas: S, options?: Options): object_ +function object_< + S extends { [x: string]: t.Predicate }, + T extends { [K in keyof S]: t.Entry } +>(schemas: S, options?: Options): object_ +function object_(schemas: S, options?: Options) { + return object_.def(schemas, options) +} + +namespace object_ { + export let proto = {} as object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ + export function def(xs: T, $?: Options, opt_?: string[]): {} { + let userDefinitions: Record = { + //<%= terms %> + } + const keys = Object_keys(xs) + const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => t.optional.is(xs[k])) + const req = keys.filter((k) => !t.optional.is(xs[k])) + const objectGuard = Predicate.record$(t.isPredicate)(xs) + ? Predicate.object$(fn.map(xs, replaceBooleanConstructor), applyOptions($)) + : Predicate.is.anyObject + function ObjectSchema(src: unknown) { return objectGuard(src) } + ObjectSchema.tag = URI.object + ObjectSchema.def = xs + ObjectSchema.opt = opt + ObjectSchema.req = req + Object_assign(ObjectSchema, proto) + return Object_assign(ObjectSchema, bindUserDefinitions(ObjectSchema, userDefinitions)) + } +} + +declare namespace object_ { + interface core { + tag: URI.object + def: S + opt: Optional + req: Required + _type: object_.type + (u: unknown): u is this['_type'] + } + type type< + S, + Opt extends Optional = Optional, + Req extends Required = Required, + T = Force< + & { [K in Req]-?: S[K]['_type' & keyof S[K]] } + & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } + > + > = never | T + type Optional = never | + string extends K ? string : K extends K ? S[K] extends t.bottom | t.optional ? K : never : never + type Required = never | + string extends K ? string : K extends K ? S[K] extends t.bottom | t.optional ? never : K : never +} + +function replaceBooleanConstructor(fn: T): t.LowerBound +function replaceBooleanConstructor(fn: T) { + return fn === globalThis.Boolean ? t.nonnullable : fn +} diff --git a/packages/schema-generator/test/test-data/object/equals.ts b/packages/schema-generator/test/test-data/object/equals.ts index e69de29b..3365f521 100644 --- a/packages/schema-generator/test/test-data/object/equals.ts +++ b/packages/schema-generator/test/test-data/object/equals.ts @@ -0,0 +1,32 @@ +import type * as T from '@traversable/registry' +import { + Array_isArray, + Object_hasOwn, + Object_is, +} from '@traversable/registry' +import type { t } from '@traversable/schema' + +export type equals = never | T.Equal +export function equals>(objectSchema: S): equals +export function equals(objectSchema: S): equals +export function equals(objectSchema: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { + return (l, r) => { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + for (const k in objectSchema.def) { + const lHas = Object_hasOwn(l, k) + const rHas = Object_hasOwn(r, k) + if (lHas) { + if (!rHas) return false + if (!objectSchema.def[k].equals(l[k], r[k])) return false + } + if (rHas) { + if (!lHas) return false + if (!objectSchema.def[k].equals(l[k], r[k])) return false + } + if (!objectSchema.def[k].equals(l[k], r[k])) return false + } + return true + } +} diff --git a/packages/schema-generator/test/test-data/object/extension.ts b/packages/schema-generator/test/test-data/object/extension.ts index e69de29b..50a1fa22 100644 --- a/packages/schema-generator/test/test-data/object/extension.ts +++ b/packages/schema-generator/test/test-data/object/extension.ts @@ -0,0 +1,18 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export let extension = { + equals, + toJsonSchema, + toString, + validate, +} + +export interface Extension { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} diff --git a/packages/schema-generator/test/test-data/object/toJsonSchema.ts b/packages/schema-generator/test/test-data/object/toJsonSchema.ts index e69de29b..4a6b8d93 100644 --- a/packages/schema-generator/test/test-data/object/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/object/toJsonSchema.ts @@ -0,0 +1,28 @@ +import type { Returns } from '@traversable/registry' +import { fn, Object_keys } from '@traversable/registry' +import type { RequiredKeys } from '@traversable/schema-to-json-schema' +import { isRequired, property } from '@traversable/schema-to-json-schema' +import { t } from '@traversable/schema' + +export interface toJsonSchema = RequiredKeys> { + toJsonSchema(): { + type: 'object' + required: { [I in keyof KS]: KS[I] & string } + properties: { [K in keyof T]: Returns } + } +} + +export function toJsonSchema(objectSchema: S): toJsonSchema +export function toJsonSchema>(objectSchema: T): { + type: 'object' + required: { [I in keyof KS]: KS[I] & string } + properties: { [K in keyof T]: Returns } +} +export function toJsonSchema({ def }: { def: { [x: string]: unknown } }) { + const required = Object_keys(def).filter(isRequired(def)) + return { + type: 'object', + required, + properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), + } as never +} diff --git a/packages/schema-generator/test/test-data/object/toString.ts b/packages/schema-generator/test/test-data/object/toString.ts index e69de29b..e1c21deb 100644 --- a/packages/schema-generator/test/test-data/object/toString.ts +++ b/packages/schema-generator/test/test-data/object/toString.ts @@ -0,0 +1,36 @@ +import type { Returns, Join, UnionToTuple } from '@traversable/registry' +import { symbol } from '@traversable/registry' +import { t } from '@traversable/schema' + +/** @internal */ +type Symbol_optional = typeof Symbol_optional +const Symbol_optional: typeof symbol.optional = symbol.optional + +/** @internal */ +const hasOptionalSymbol = (u: unknown): u is { toString(): T } => + !!u && typeof u === 'function' + && Symbol_optional in u + && typeof u[Symbol_optional] === 'number' + +/** @internal */ +const hasToString = (x: unknown): x is { toString(): string } => + !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' + +export type toString> = never + | [keyof T] extends [never] ? '{}' + /* @ts-expect-error */ + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${Returns}` }, ', '>} }` + + +export function toString>(objectSchema: t.object): toString +export function toString({ def }: t.object) { + if (!!def && typeof def === 'object') { + const entries = Object.entries(def) + if (entries.length === 0) return '{}' + else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" + }: ${hasToString(x) ? x.toString() : 'unknown' + }`).join(', ') + } }` + } + else return '{ [x: string]: unknown }' +} diff --git a/packages/schema-generator/test/test-data/object/validate.ts b/packages/schema-generator/test/test-data/object/validate.ts index e69de29b..0cc1a718 100644 --- a/packages/schema-generator/test/test-data/object/validate.ts +++ b/packages/schema-generator/test/test-data/object/validate.ts @@ -0,0 +1,118 @@ +import type { Unknown } from '@traversable/registry' +import { + Array_isArray, + Object_keys, + Object_hasOwn, + typeName, + URI, +} from '@traversable/registry' +import { t, getConfig } from '@traversable/schema' +import type { ValidationError, Validator, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors, Errors, UnaryErrors } from '@traversable/derive-validators' + +/** @internal */ +let isObject = (u: unknown): u is { [x: string]: unknown } => + !!u && typeof u === 'object' && !Array_isArray(u) + +/** @internal */ +let isKeyOf = (k: keyof any, u: T): k is keyof T => + !!u && (typeof u === 'function' || typeof u === 'object') && k in u + +/** @internal */ +let isOptional = t.has('tag', t.eq(URI.optional)) + +validate.tag = URI.object + +export type validate = never | ValidationFn + +export function validate( + objectSchema: t.object, + u: t.object['_type'] | Unknown, + path?: (keyof any)[] +): true | ValidationError[] + +export function validate( + objectSchema: t.object, + u: t.object['_type'] | Unknown, + path?: (keyof any)[] +): true | ValidationError[] + +export function validate( + objectSchema: t.object, + u: unknown, + path: (keyof any)[] = [] +): true | ValidationError[] { + if (!isObject(u)) return [Errors.object(u, path)] + let errors = Array.of() + let { schema: { optionalTreatment } } = getConfig() + let keys = Object_keys(objectSchema.def) + if (optionalTreatment === 'exactOptional') { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path_ = [...path, k] + if (Object_hasOwn(u, k) && u[k] === undefined) { + if (isOptional(objectSchema.def[k].validate)) { + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + let args = [u[k], path_, tag] as never as [unknown, (keyof any)[]] + errors.push(NullaryErrors[tag](...args)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path_)) + } + } + let results = objectSchema.def[k].validate(u[k], path_) + if (results === true) continue + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + errors.push(NullaryErrors[tag](u[k], path_, tag)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag].invalid(u[k], path_)) + } + errors.push(...results) + } + else if (Object_hasOwn(u, k)) { + let results = objectSchema.def[k].validate(u[k], path_) + if (results === true) continue + errors.push(...results) + continue + } else { + errors.push(UnaryErrors.object.missing(u, path_)) + continue + } + } + } + else { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path_ = [...path, k] + if (!Object_hasOwn(u, k)) { + if (!isOptional(objectSchema.def[k].validate)) { + errors.push(UnaryErrors.object.missing(u, path_)) + continue + } + else { + if (!Object_hasOwn(u, k)) continue + if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { + if (u[k] === undefined) continue + let results = objectSchema.def[k].validate(u[k], path_) + if (results === true) continue + for (let j = 0; j < results.length; j++) { + let result = results[j] + errors.push(result) + continue + } + } + } + } + let results = objectSchema.def[k].validate(u[k], path_) + if (results === true) continue + for (let l = 0; l < results.length; l++) { + let result = results[l] + errors.push(result) + } + } + } + return errors.length === 0 || errors +} diff --git a/packages/schema-to-json-schema/src/exports.ts b/packages/schema-to-json-schema/src/exports.ts index ac69290b..27f952a7 100644 --- a/packages/schema-to-json-schema/src/exports.ts +++ b/packages/schema-to-json-schema/src/exports.ts @@ -4,4 +4,6 @@ type JsonSchema = import('./jsonSchema.js').JsonSchema export { JsonSchema } export { toJsonSchema, fromJsonSchema } from './recursive.js' export { VERSION } from './version.js' +export type { RequiredKeys } from './properties.js' +export { isRequired, property } from './properties.js' export type * from './specification.js' From d5b6fc31038ba7d6db0438dd0e3a3f0616973e71 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 9 Apr 2025 16:44:08 -0500 Subject: [PATCH 14/45] feat(generator): splits out tuple.core --- packages/registry/src/exports.ts | 3 + packages/registry/src/mapObject.ts | 19 +++++ .../registry/src/replaceBooleanConstructor.ts | 7 ++ .../test/test-data/object/core.ts | 23 ++---- .../test/test-data/tuple/core.ts | 77 +++++++++++++++++++ .../test/test-data/tuple/equals.ts | 0 .../test/test-data/tuple/extension.ts | 0 .../test/test-data/tuple/toJsonSchema.ts | 0 .../test/test-data/tuple/toString.ts | 0 .../test/test-data/tuple/validate.ts | 0 packages/schema/src/exports.ts | 2 + packages/schema/src/namespace.ts | 2 + 12 files changed, 118 insertions(+), 15 deletions(-) create mode 100644 packages/registry/src/mapObject.ts create mode 100644 packages/registry/src/replaceBooleanConstructor.ts create mode 100644 packages/schema-generator/test/test-data/tuple/core.ts create mode 100644 packages/schema-generator/test/test-data/tuple/equals.ts create mode 100644 packages/schema-generator/test/test-data/tuple/extension.ts create mode 100644 packages/schema-generator/test/test-data/tuple/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/tuple/toString.ts create mode 100644 packages/schema-generator/test/test-data/tuple/validate.ts diff --git a/packages/registry/src/exports.ts b/packages/registry/src/exports.ts index 478ab08a..acd6b2ad 100644 --- a/packages/registry/src/exports.ts +++ b/packages/registry/src/exports.ts @@ -44,6 +44,9 @@ export { export { unsafeCompact } from './compact.js' export { bindUserDefinitions } from './bindUserDefinitions.js' +export { replaceBooleanConstructor } from './replaceBooleanConstructor.js' + +export { map } from './mapObject.js' export { objectFromKeys, diff --git a/packages/registry/src/mapObject.ts b/packages/registry/src/mapObject.ts new file mode 100644 index 00000000..d33ea6e9 --- /dev/null +++ b/packages/registry/src/mapObject.ts @@ -0,0 +1,19 @@ + +/** @internal */ +let Array_isArray = globalThis.Array.isArray + +/** @internal */ +let Object_keys = globalThis.Object.keys + +export function map(src: S, mapfn: (x: S[keyof S], k: string, xs: S) => T): { [x: string]: T } +export function map(src: Record, mapfn: (x: S, k: string, xs: Record) => T) { + if (Array_isArray(src)) return src.map(mapfn as never) + const keys = Object_keys(src) + let out: { [x: string]: T } = {} + for (let ix = 0, len = keys.length; ix < len; ix++) { + const k = keys[ix] + out[k] = mapfn(src[k], k, src) + } + return out +} + diff --git a/packages/registry/src/replaceBooleanConstructor.ts b/packages/registry/src/replaceBooleanConstructor.ts new file mode 100644 index 00000000..9fbec604 --- /dev/null +++ b/packages/registry/src/replaceBooleanConstructor.ts @@ -0,0 +1,7 @@ +/** @internal */ +const isBooleanConstructor = (u: unknown): u is globalThis.BooleanConstructor => u === globalThis.Boolean + +export function replaceBooleanConstructor(replacement: (u: unknown) => u is {}): + (fn: typeof globalThis.Boolean | T) => T | ((u: unknown) => u is {}) { + return (fn) => isBooleanConstructor(fn) ? replacement : fn +} diff --git a/packages/schema-generator/test/test-data/object/core.ts b/packages/schema-generator/test/test-data/object/core.ts index 25e5747e..c44696a7 100644 --- a/packages/schema-generator/test/test-data/object/core.ts +++ b/packages/schema-generator/test/test-data/object/core.ts @@ -6,6 +6,7 @@ import { fn, Object_assign, Object_keys, + replaceBooleanConstructor, URI, } from '@traversable/registry' import type { SchemaOptions as Options } from '@traversable/schema' @@ -28,6 +29,8 @@ function object_(schemas: S, options?: Opti return object_.def(schemas, options) } +const replaceBoolean = replaceBooleanConstructor(t.nonnullable) + namespace object_ { export let proto = {} as object_ export function def(xs: T, $?: Options, opt?: string[]): object_ @@ -38,9 +41,8 @@ namespace object_ { const keys = Object_keys(xs) const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => t.optional.is(xs[k])) const req = keys.filter((k) => !t.optional.is(xs[k])) - const objectGuard = Predicate.record$(t.isPredicate)(xs) - ? Predicate.object$(fn.map(xs, replaceBooleanConstructor), applyOptions($)) - : Predicate.is.anyObject + const objectGuard = !Predicate.record$(t.isPredicate)(xs) ? Predicate.is.anyObject + : Predicate.object$(fn.map(xs, (x) => replaceBoolean(x as never)), applyOptions($)) function ObjectSchema(src: unknown) { return objectGuard(src) } ObjectSchema.tag = URI.object ObjectSchema.def = xs @@ -55,27 +57,18 @@ declare namespace object_ { interface core { tag: URI.object def: S - opt: Optional + opt: t.Optional req: Required _type: object_.type (u: unknown): u is this['_type'] } type type< S, - Opt extends Optional = Optional, - Req extends Required = Required, + Opt extends t.Optional = t.Optional, + Req extends t.Required = t.Required, T = Force< & { [K in Req]-?: S[K]['_type' & keyof S[K]] } & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } > > = never | T - type Optional = never | - string extends K ? string : K extends K ? S[K] extends t.bottom | t.optional ? K : never : never - type Required = never | - string extends K ? string : K extends K ? S[K] extends t.bottom | t.optional ? never : K : never -} - -function replaceBooleanConstructor(fn: T): t.LowerBound -function replaceBooleanConstructor(fn: T) { - return fn === globalThis.Boolean ? t.nonnullable : fn } diff --git a/packages/schema-generator/test/test-data/tuple/core.ts b/packages/schema-generator/test/test-data/tuple/core.ts new file mode 100644 index 00000000..304697f6 --- /dev/null +++ b/packages/schema-generator/test/test-data/tuple/core.ts @@ -0,0 +1,77 @@ +import type { + SchemaOptions as Options, + TypeError, +} from '@traversable/registry' + +import { + getConfig, + Object_assign, + parseArgs, + replaceBooleanConstructor, + URI +} from '@traversable/registry' + +import type { + Label, + ValidateTuple, +} from '@traversable/schema' +import { + t, + Predicate, + __within as within, +} from '@traversable/schema' + + +export type FirstOptionalItem + = S extends readonly [infer H, ...infer T] ? t.optional extends H ? Offset['length'] : FirstOptionalItem : never + ; + +export type TupleType = never + | t.optional extends T[number & keyof T] + ? T extends readonly [infer Head, ...infer Tail] + ? [Head] extends [t.optional] ? Label< + { [ix in keyof Out]: Out[ix]['_type' & keyof Out[ix]] }, + { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } + > + : TupleType + : never + : { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } + ; + +const replaceBoolean = replaceBooleanConstructor(t.nonnullable) + + +export { tuple } +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...args: | [...S] | [...S, Options]) { return tuple.def(...parseArgs(getConfig().schema, args)) } +interface tuple { (u: unknown): u is this['_type'], tag: URI.tuple, def: S, _type: TupleType, opt: FirstOptionalItem } +namespace tuple { + export let prototype = { tag: URI.tuple } as tuple + export type type> = never | T + export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple + /* v8 ignore next 1 */ + export function def(xs: readonly [...T], $: Options = getConfig().schema, opt_?: number) { + const opt = opt_ || xs.findIndex(t.optional.is) + const options = { + ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(t.optional.is) + } satisfies tuple.InternalOptions + const tupleGuard = xs.every(t.isPredicate) + ? Predicate.tuple$(options)(xs.map(replaceBoolean)) + : Predicate.is.anyArray + function TupleSchema(src: unknown) { return tupleGuard(src) } + TupleSchema.def = xs + TupleSchema.opt = opt + return Object_assign(TupleSchema, tuple.prototype) + } +} +declare namespace tuple { + type validate = ValidateTuple> + type from + = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? t.invalid> : V[I] } : T + type InternalOptions = { minLength?: number } +} diff --git a/packages/schema-generator/test/test-data/tuple/equals.ts b/packages/schema-generator/test/test-data/tuple/equals.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/tuple/extension.ts b/packages/schema-generator/test/test-data/tuple/extension.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/tuple/toString.ts b/packages/schema-generator/test/test-data/tuple/toString.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema-generator/test/test-data/tuple/validate.ts b/packages/schema-generator/test/test-data/tuple/validate.ts new file mode 100644 index 00000000..e69de29b diff --git a/packages/schema/src/exports.ts b/packages/schema/src/exports.ts index dff46050..5d29b65d 100644 --- a/packages/schema/src/exports.ts +++ b/packages/schema/src/exports.ts @@ -67,7 +67,9 @@ export { clone } from './clone.js' export type { Bounds } from './bounded.js' export type { Guard, + Label, Typeguard, + ValidateTuple, } from './types.js' export { get, get$ } from './utils.js' diff --git a/packages/schema/src/namespace.ts b/packages/schema/src/namespace.ts index baa2d4aa..3a358da8 100644 --- a/packages/schema/src/namespace.ts +++ b/packages/schema/src/namespace.ts @@ -12,6 +12,8 @@ export type { Leaf, LowerBound, Predicate, + Optional, + Required, Schema, Tag, top, From da6c16e3a1ff1f095ec666716fdc42e4a5fb859e Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 9 Apr 2025 18:16:45 -0500 Subject: [PATCH 15/45] feat(generator): adds generator for union schemas --- packages/derive-equals/src/install.ts | 5 --- packages/derive-validators/src/exports.ts | 2 +- packages/derive-validators/src/prototype.ts | 12 +++--- packages/derive-validators/src/recursive.ts | 4 +- packages/derive-validators/src/shared.ts | 2 +- packages/registry/src/globalThis.ts | 1 + packages/schema-generator/package.json | 3 +- .../schema-generator/test/imports.test.ts | 18 ++++++++ .../test/test-data/object/core.ts | 6 +-- .../test/test-data/object/equals.ts | 4 +- .../test/test-data/object/toString.ts | 4 +- .../test/test-data/tuple/core.ts | 27 ++++++++---- .../test/test-data/tuple/equals.ts | 26 ++++++++++++ .../test/test-data/tuple/extension.ts | 18 ++++++++ .../test/test-data/tuple/toJsonSchema.ts | 25 +++++++++++ .../test/test-data/tuple/toString.ts | 21 ++++++++++ .../test/test-data/tuple/validate.ts | 34 +++++++++++++++ .../test/test-data/union/core.ts | 42 +++++++++++++++++++ .../test/test-data/union/equals.ts | 15 +++++++ .../test/test-data/union/extension.ts | 18 ++++++++ .../test/test-data/union/toJsonSchema.ts | 15 +++++++ .../test/test-data/union/toString.ts | 13 ++++++ .../test/test-data/union/validate.ts | 26 ++++++++++++ packages/schema-generator/tsconfig.test.json | 3 +- packages/schema-to-json-schema/src/exports.ts | 5 ++- .../schema-to-json-schema/src/jsonSchema.ts | 2 +- packages/schema-to-string/src/exports.ts | 5 +++ packages/schema-to-string/src/shared.ts | 12 ++++++ packages/schema-to-string/src/toString.ts | 12 +----- pnpm-lock.yaml | 3 ++ 30 files changed, 338 insertions(+), 45 deletions(-) create mode 100644 packages/schema-generator/test/test-data/union/core.ts create mode 100644 packages/schema-generator/test/test-data/union/equals.ts create mode 100644 packages/schema-generator/test/test-data/union/extension.ts create mode 100644 packages/schema-generator/test/test-data/union/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/union/toString.ts create mode 100644 packages/schema-generator/test/test-data/union/validate.ts create mode 100644 packages/schema-to-string/src/shared.ts diff --git a/packages/derive-equals/src/install.ts b/packages/derive-equals/src/install.ts index b844f3c3..078c8fa1 100644 --- a/packages/derive-equals/src/install.ts +++ b/packages/derive-equals/src/install.ts @@ -31,11 +31,6 @@ declare module '@traversable/schema' { interface t_object extends equals { } } -/** @internal */ -const hasEquals - : (u: unknown) => u is { equals: Equal } - = has('equals', (u): u is Equal => typeof u === 'function' && u.length === 2) - /** @internal */ const Object_assign = globalThis.Object.assign diff --git a/packages/derive-validators/src/exports.ts b/packages/derive-validators/src/exports.ts index a412e6a1..4a7db2da 100644 --- a/packages/derive-validators/src/exports.ts +++ b/packages/derive-validators/src/exports.ts @@ -7,7 +7,7 @@ export type { Validate, Options, } from './shared.js' -export { isOptional } from './shared.js' +export { hasOptionalSymbol } from './shared.js' export type { ValidationError, diff --git a/packages/derive-validators/src/prototype.ts b/packages/derive-validators/src/prototype.ts index d5e5eaa6..d8678d0d 100644 --- a/packages/derive-validators/src/prototype.ts +++ b/packages/derive-validators/src/prototype.ts @@ -46,8 +46,6 @@ let isObject = (u: unknown): u is { [x: string]: unknown } => /** @internal */ let isKeyOf = (k: keyof any, u: T): k is keyof T => !!u && (typeof u === 'function' || typeof u === 'object') && k in u -const isOptional = t.has('tag', t.eq(URI.optional)) - function validateNever(this: t.never, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.never(u, path)] } function validateUnknown(this: t.unknown, u: unknown, path: (keyof any)[] = []) { return true } function validateAny(this: t.any, u: unknown, path: (keyof any)[] = []) { return true } @@ -127,7 +125,7 @@ function validateRecord(this: t.record, u: unknown, path // validateUnion.optional = 0 validateUnion.tag = URI.union function validateUnion(this: t.union, u: unknown, path: (keyof any)[] = []) { - // if (this.def.every((x) => isOptional(x.validate))) validateUnion.optional = 1; + // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; let errors = Array.of() for (let i = 0; i < this.def.length; i++) { let results = this.def[i].validate(u, path) @@ -157,7 +155,7 @@ function validateTuple(this: t.tuple, u: unknown, path: (k let errors = Array.of() if (!Array_isArray(u)) return [ERROR.array(u, path)] for (let i = 0; i < this.def.length; i++) { - if (!(i in u) && !(isOptional(this.def[i].validate))) { + if (!(i in u) && !(t.optional.is(this.def[i].validate))) { errors.push(ERROR.missingIndex(u, [...path, i])) continue } @@ -187,7 +185,7 @@ function validateObject(this: t.object<{ [x: string]: Validator }>, u: unknown, let k = keys[i] let path_ = [...path, k] if (hasOwn(u, k) && u[k] === undefined) { - if (isOptional(this.def[k].validate)) { + if (t.optional.is(this.def[k].validate)) { let tag = typeName(this.def[k].validate) if (isKeyOf(tag, NULLARY)) { let args = [u[k], path_, tag] as never as [unknown, (keyof any)[]] @@ -225,13 +223,13 @@ function validateObject(this: t.object<{ [x: string]: Validator }>, u: unknown, let k = keys[i] let path_ = [...path, k] if (!hasOwn(u, k)) { - if (!isOptional(this.def[k].validate)) { + if (!t.optional.is(this.def[k].validate)) { errors.push(UNARY.object.missing(u, path_)) continue } else { if (!hasOwn(u, k)) continue - if (isOptional(this.def[k].validate) && hasOwn(u, k)) { + if (t.optional.is(this.def[k].validate) && hasOwn(u, k)) { if (u[k] === undefined) continue let results = this.def[k].validate(u[k], path_) if (results === true) continue diff --git a/packages/derive-validators/src/recursive.ts b/packages/derive-validators/src/recursive.ts index f47d4e76..a1b3cd9b 100644 --- a/packages/derive-validators/src/recursive.ts +++ b/packages/derive-validators/src/recursive.ts @@ -5,7 +5,7 @@ import { t, getConfig } from '@traversable/schema' import type { ValidationError } from './errors.js' import { BOUNDS, ERROR, UNARY } from './errors.js' import type { Options, ValidationFn } from './shared.js' -import { isOptional } from './shared.js' +import { hasOptionalSymbol } from './shared.js' /** @internal */ const Array_isArray = globalThis.Array.isArray @@ -282,7 +282,7 @@ const union } return errors.length > 0 ? errors : true } - if (validationFns.every(isOptional)) validateUnion[symbol.optional] = true + if (validationFns.every(hasOptionalSymbol)) validateUnion[symbol.optional] = true validateUnion.tag = URI.union validateUnion.ctx = Array.of() return validateUnion diff --git a/packages/derive-validators/src/shared.ts b/packages/derive-validators/src/shared.ts index e0bd1501..e01d86b3 100644 --- a/packages/derive-validators/src/shared.ts +++ b/packages/derive-validators/src/shared.ts @@ -16,5 +16,5 @@ export type ValidationFn = never | { export interface Validator { validate: ValidationFn } -export const isOptional = (u: unknown): u is t.optional => +export const hasOptionalSymbol = (u: unknown): u is t.optional => !!u && typeof u === 'function' && symbol.optional in u && typeof u[symbol.optional] === 'number' diff --git a/packages/registry/src/globalThis.ts b/packages/registry/src/globalThis.ts index 02b745a4..a2a49ddb 100644 --- a/packages/registry/src/globalThis.ts +++ b/packages/registry/src/globalThis.ts @@ -34,6 +34,7 @@ export const Object_keys : (x: T) => (K)[] = globalThis.Object.keys + export type Object_entries = never | (K extends K ? [k: K, v: T[K & keyof T]] : never)[] export const Object_entries: { >(x: T): MixedNonFiniteEntries diff --git a/packages/schema-generator/package.json b/packages/schema-generator/package.json index 031c57e7..fa6cecb4 100644 --- a/packages/schema-generator/package.json +++ b/packages/schema-generator/package.json @@ -43,6 +43,7 @@ "@traversable/derive-validators": "workspace:^", "@traversable/schema-to-json-schema": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^" } } diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index db5c6cc0..499a1a2a 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -42,10 +42,28 @@ let PATH = { toString: path.join(DATA_PATH, 'object', 'toString.ts'), validate: path.join(DATA_PATH, 'object', 'validate.ts'), }, + tuple: { + core: path.join(DATA_PATH, 'tuple', 'core.ts'), + extension: path.join(DATA_PATH, 'tuple', 'extension.ts'), + equals: path.join(DATA_PATH, 'tuple', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'tuple', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'tuple', 'toString.ts'), + validate: path.join(DATA_PATH, 'tuple', 'validate.ts'), + }, + union: { + core: path.join(DATA_PATH, 'union', 'core.ts'), + extension: path.join(DATA_PATH, 'union', 'extension.ts'), + equals: path.join(DATA_PATH, 'union', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'union', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'union', 'toString.ts'), + validate: path.join(DATA_PATH, 'union', 'validate.ts'), + }, }, targets: { array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), object: path.join(DIR_PATH, '__generated__', 'object.gen.ts'), + tuple: path.join(DIR_PATH, '__generated__', 'tuple.gen.ts'), + union: path.join(DIR_PATH, '__generated__', 'union.gen.ts'), string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), } } diff --git a/packages/schema-generator/test/test-data/object/core.ts b/packages/schema-generator/test/test-data/object/core.ts index c44696a7..3a80c1c5 100644 --- a/packages/schema-generator/test/test-data/object/core.ts +++ b/packages/schema-generator/test/test-data/object/core.ts @@ -3,7 +3,7 @@ import { Array_isArray, applyOptions, bindUserDefinitions, - fn, + map, Object_assign, Object_keys, replaceBooleanConstructor, @@ -34,7 +34,7 @@ const replaceBoolean = replaceBooleanConstructor(t.nonnullable) namespace object_ { export let proto = {} as object_ export function def(xs: T, $?: Options, opt?: string[]): object_ - export function def(xs: T, $?: Options, opt_?: string[]): {} { + export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { let userDefinitions: Record = { //<%= terms %> } @@ -42,7 +42,7 @@ namespace object_ { const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => t.optional.is(xs[k])) const req = keys.filter((k) => !t.optional.is(xs[k])) const objectGuard = !Predicate.record$(t.isPredicate)(xs) ? Predicate.is.anyObject - : Predicate.object$(fn.map(xs, (x) => replaceBoolean(x as never)), applyOptions($)) + : Predicate.is.object(map(xs, replaceBoolean), applyOptions($)) function ObjectSchema(src: unknown) { return objectGuard(src) } ObjectSchema.tag = URI.object ObjectSchema.def = xs diff --git a/packages/schema-generator/test/test-data/object/equals.ts b/packages/schema-generator/test/test-data/object/equals.ts index 3365f521..c340a938 100644 --- a/packages/schema-generator/test/test-data/object/equals.ts +++ b/packages/schema-generator/test/test-data/object/equals.ts @@ -7,8 +7,8 @@ import { import type { t } from '@traversable/schema' export type equals = never | T.Equal -export function equals>(objectSchema: S): equals -export function equals(objectSchema: S): equals +export function equals(objectSchema: t.object): equals> +export function equals(objectSchema: t.object): equals> export function equals(objectSchema: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { return (l, r) => { if (Object_is(l, r)) return true diff --git a/packages/schema-generator/test/test-data/object/toString.ts b/packages/schema-generator/test/test-data/object/toString.ts index e1c21deb..47e2afe7 100644 --- a/packages/schema-generator/test/test-data/object/toString.ts +++ b/packages/schema-generator/test/test-data/object/toString.ts @@ -1,4 +1,4 @@ -import type { Returns, Join, UnionToTuple } from '@traversable/registry' +import type { Join, UnionToTuple } from '@traversable/registry' import { symbol } from '@traversable/registry' import { t } from '@traversable/schema' @@ -19,7 +19,7 @@ const hasToString = (x: unknown): x is { toString(): string } => export type toString> = never | [keyof T] extends [never] ? '{}' /* @ts-expect-error */ - : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${Returns}` }, ', '>} }` + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` export function toString>(objectSchema: t.object): toString diff --git a/packages/schema-generator/test/test-data/tuple/core.ts b/packages/schema-generator/test/test-data/tuple/core.ts index 304697f6..0d7b8231 100644 --- a/packages/schema-generator/test/test-data/tuple/core.ts +++ b/packages/schema-generator/test/test-data/tuple/core.ts @@ -4,6 +4,7 @@ import type { } from '@traversable/registry' import { + bindUserDefinitions, getConfig, Object_assign, parseArgs, @@ -18,7 +19,6 @@ import type { import { t, Predicate, - __within as within, } from '@traversable/schema' @@ -40,6 +40,9 @@ export type TupleType = never const replaceBoolean = replaceBooleanConstructor(t.nonnullable) +interface tuple extends tuple.core { + //<%= types %> +} export { tuple } function tuple(...schemas: tuple.validate): tuple, S>> @@ -49,27 +52,37 @@ function tuple(...schemas: tuple.validate): tuple, S>> function tuple }>(...schemas: tuple.validate): tuple, T>> function tuple(...args: | [...S] | [...S, Options]) { return tuple.def(...parseArgs(getConfig().schema, args)) } -interface tuple { (u: unknown): u is this['_type'], tag: URI.tuple, def: S, _type: TupleType, opt: FirstOptionalItem } namespace tuple { - export let prototype = { tag: URI.tuple } as tuple + export let proto = {} as tuple export type type> = never | T export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple - /* v8 ignore next 1 */ - export function def(xs: readonly [...T], $: Options = getConfig().schema, opt_?: number) { + export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { + let userDefinitions: Record = { + //<%= terms %> + } const opt = opt_ || xs.findIndex(t.optional.is) const options = { ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(t.optional.is) } satisfies tuple.InternalOptions const tupleGuard = xs.every(t.isPredicate) - ? Predicate.tuple$(options)(xs.map(replaceBoolean)) + ? Predicate.is.tuple(options)(xs.map(replaceBoolean)) : Predicate.is.anyArray function TupleSchema(src: unknown) { return tupleGuard(src) } + TupleSchema.tag = URI.tuple TupleSchema.def = xs TupleSchema.opt = opt - return Object_assign(TupleSchema, tuple.prototype) + Object_assign(TupleSchema, tuple.proto) + return Object_assign(TupleSchema, bindUserDefinitions(TupleSchema, userDefinitions)) } } declare namespace tuple { + interface core { + (u: unknown): u is this['_type'] + tag: URI.tuple + def: S + _type: TupleType + opt: FirstOptionalItem + } type validate = ValidateTuple> type from = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? t.invalid> : V[I] } : T diff --git a/packages/schema-generator/test/test-data/tuple/equals.ts b/packages/schema-generator/test/test-data/tuple/equals.ts index e69de29b..6e0adac4 100644 --- a/packages/schema-generator/test/test-data/tuple/equals.ts +++ b/packages/schema-generator/test/test-data/tuple/equals.ts @@ -0,0 +1,26 @@ +import type { Equal } from '@traversable/registry' +import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' +import { t } from '@traversable/schema' + +export type equals = Equal + +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple) { + return (l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean => { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + for (let ix = tupleSchema.def.length; ix-- !== 0;) { + if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue + if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false + if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false + if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { + if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false + } + } + return true + } + return false + } +} diff --git a/packages/schema-generator/test/test-data/tuple/extension.ts b/packages/schema-generator/test/test-data/tuple/extension.ts index e69de29b..5af1315c 100644 --- a/packages/schema-generator/test/test-data/tuple/extension.ts +++ b/packages/schema-generator/test/test-data/tuple/extension.ts @@ -0,0 +1,18 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Extension { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let extension = { + equals, + toJsonSchema, + toString, + validate, +} diff --git a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts index e69de29b..8e7ea956 100644 --- a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts @@ -0,0 +1,25 @@ +import type { Returns } from '@traversable/registry' +import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' +import type { MinItems } from '@traversable/schema-to-json-schema' +import type { t } from '@traversable/schema' + +export interface toJsonSchema { + type: 'array', + items: { [I in keyof T]: Returns } + additionalItems: false + minItems: MinItems + maxItems: T['length' & keyof T] +} + +export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema +export function toJsonSchema({ def }: t.tuple) { + const min = minItems(def) + const max = def.length + return { + type: 'array' as const, + additionalItems: false as const, + items: applyTupleOptionality(def, { min, max }) as never, + minItems: min as never, + maxItems: max, + } +} diff --git a/packages/schema-generator/test/test-data/tuple/toString.ts b/packages/schema-generator/test/test-data/tuple/toString.ts index e69de29b..c4943b72 100644 --- a/packages/schema-generator/test/test-data/tuple/toString.ts +++ b/packages/schema-generator/test/test-data/tuple/toString.ts @@ -0,0 +1,21 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import { t } from '@traversable/schema' +import { hasToString } from '@traversable/schema-to-string' + +export type toString = never | `[${Join<{ + [I in keyof T]: `${ + /* @ts-expect-error */ + T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType + }` +}, ', '>}]` + +export function toString(tupleSchema: t.tuple): toString +export function toString(tupleSchema: t.tuple): string { + return Array_isArray(tupleSchema.def) + ? `[${tupleSchema.def.map( + (x) => t.optional.is(x) + ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` + : hasToString(x) ? x.toString() : 'unknown' + ).join(', ')}]` : 'unknown[]' +} diff --git a/packages/schema-generator/test/test-data/tuple/validate.ts b/packages/schema-generator/test/test-data/tuple/validate.ts index e69de29b..875c1dbb 100644 --- a/packages/schema-generator/test/test-data/tuple/validate.ts +++ b/packages/schema-generator/test/test-data/tuple/validate.ts @@ -0,0 +1,34 @@ +import { URI, Array_isArray } from '@traversable/registry' +import { t } from '@traversable/schema' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + +validate.tag = URI.tuple + +export type validate = Validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): Validate { + return (u, path = []) => { + let errors = Array.of() + if (!Array_isArray(u)) return [Errors.array(u, path)] + for (let i = 0; i < tupleSchema.def.length; i++) { + if (!(i in u) && !(t.optional.is(tupleSchema.def[i].validate))) { + errors.push(Errors.missingIndex(u, [...path, i])) + continue + } + let results = tupleSchema.def[i].validate(u[i], [...path, i]) + if (results !== true) { + for (let j = 0; j < results.length; j++) errors.push(results[j]) + results.push(Errors.arrayElement(u[i], [...path, i])) + } + } + if (u.length > tupleSchema.def.length) { + for (let k = tupleSchema.def.length; k < u.length; k++) { + let excess = u[k] + errors.push(Errors.excessItems(excess, [...path, k])) + } + } + return errors.length === 0 || errors + } +} diff --git a/packages/schema-generator/test/test-data/union/core.ts b/packages/schema-generator/test/test-data/union/core.ts new file mode 100644 index 00000000..a6363ae4 --- /dev/null +++ b/packages/schema-generator/test/test-data/union/core.ts @@ -0,0 +1,42 @@ +import { + bindUserDefinitions, + Object_assign, + URI +} from '@traversable/registry' + +import { + t, + Predicate, +} from '@traversable/schema' + +export interface union extends union.core { + //<%= types %> +} + +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: S): {} { return union.def(schemas) } +export namespace union { + export let proto = {} as union + export function def(xs: T): union + export function def(xs: T): {} { + let userDefinitions = { + //<%= terms %> + } + const anyOf = xs.every(t.isPredicate) ? Predicate.is.union(xs) : Predicate.is.unknown + function UnionSchema(src: unknown) { return anyOf(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.proto) + return Object_assign(UnionSchema, bindUserDefinitions(UnionSchema, userDefinitions)) + } +} +export declare namespace union { + interface core { + (u: unknown): u is this['_type'] + tag: URI.union + def: S + _type: S[number & keyof S]['_type' & keyof S[number & keyof S]] + } + type type = never | T +} diff --git a/packages/schema-generator/test/test-data/union/equals.ts b/packages/schema-generator/test/test-data/union/equals.ts new file mode 100644 index 00000000..5233d0be --- /dev/null +++ b/packages/schema-generator/test/test-data/union/equals.ts @@ -0,0 +1,15 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema' + +export type equals = Equal +export function equals(unionSchema: t.union<[...S]>): equals +export function equals(unionSchema: t.union<[...S]>): equals +export function equals({ def }: t.union<{ equals: Equal }[]>): Equal { + return (l: unknown, r: unknown): boolean => { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (def[ix].equals(l, r)) return true + return false + } +} diff --git a/packages/schema-generator/test/test-data/union/extension.ts b/packages/schema-generator/test/test-data/union/extension.ts new file mode 100644 index 00000000..5af1315c --- /dev/null +++ b/packages/schema-generator/test/test-data/union/extension.ts @@ -0,0 +1,18 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Extension { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let extension = { + equals, + toJsonSchema, + toString, + validate, +} diff --git a/packages/schema-generator/test/test-data/union/toJsonSchema.ts b/packages/schema-generator/test/test-data/union/toJsonSchema.ts new file mode 100644 index 00000000..d578fc98 --- /dev/null +++ b/packages/schema-generator/test/test-data/union/toJsonSchema.ts @@ -0,0 +1,15 @@ +import type { Returns } from '@traversable/registry' +import { t } from '@traversable/schema' +import { getSchema } from '@traversable/schema-to-json-schema' + +export type toJsonSchema = { + anyOf: { [I in keyof S]: Returns } +} + +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema({ def }: t.union): {} { + return { + anyOf: def.map(getSchema) + } +} diff --git a/packages/schema-generator/test/test-data/union/toString.ts b/packages/schema-generator/test/test-data/union/toString.ts new file mode 100644 index 00000000..58a20dc5 --- /dev/null +++ b/packages/schema-generator/test/test-data/union/toString.ts @@ -0,0 +1,13 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import { t } from '@traversable/schema' +import { callToString } from '@traversable/schema-to-string' + +export type toString = never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` + +export function toString(unionSchema: t.union): toString +export function toString({ def }: t.union): string { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' +} diff --git a/packages/schema-generator/test/test-data/union/validate.ts b/packages/schema-generator/test/test-data/union/validate.ts new file mode 100644 index 00000000..2c398bab --- /dev/null +++ b/packages/schema-generator/test/test-data/union/validate.ts @@ -0,0 +1,26 @@ +import { URI } from '@traversable/registry' +import { t } from '@traversable/schema' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(unionSchema: t.union): validate +export function validate(unionSchema: t.union): validate +export function validate({ def }: t.union) { + validateUnion.tag = URI.union + function validateUnion(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results === true) { + // validateUnion.optional = 0 + return true + } + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + // validateUnion.optional = 0 + return errors.length === 0 || errors + } + return validateUnion +} diff --git a/packages/schema-generator/tsconfig.test.json b/packages/schema-generator/tsconfig.test.json index 5bf59844..a1e26e6a 100644 --- a/packages/schema-generator/tsconfig.test.json +++ b/packages/schema-generator/tsconfig.test.json @@ -11,7 +11,8 @@ { "path": "../derive-validators" }, { "path": "../registry" }, { "path": "../schema" }, - { "path": "../schema-to-json-schema" } + { "path": "../schema-to-json-schema" }, + { "path": "../schema-to-string" } ], "include": ["test"] } diff --git a/packages/schema-to-json-schema/src/exports.ts b/packages/schema-to-json-schema/src/exports.ts index 27f952a7..e7a62a4f 100644 --- a/packages/schema-to-json-schema/src/exports.ts +++ b/packages/schema-to-json-schema/src/exports.ts @@ -2,8 +2,11 @@ import * as JsonSchema from './jsonSchema.js' type JsonSchema = import('./jsonSchema.js').JsonSchema export { JsonSchema } +export { applyTupleOptionality } from './jsonSchema.js' export { toJsonSchema, fromJsonSchema } from './recursive.js' export { VERSION } from './version.js' export type { RequiredKeys } from './properties.js' -export { isRequired, property } from './properties.js' +export { getSchema, isRequired, property } from './properties.js' +export type { MinItems } from './items.js' +export { minItems } from './items.js' export type * from './specification.js' diff --git a/packages/schema-to-json-schema/src/jsonSchema.ts b/packages/schema-to-json-schema/src/jsonSchema.ts index 2d9c7334..9a497423 100644 --- a/packages/schema-to-json-schema/src/jsonSchema.ts +++ b/packages/schema-to-json-schema/src/jsonSchema.ts @@ -60,7 +60,7 @@ type NumberBounds = Force<{ type: 'number' } & PickIfDefined = Force<{ type: 'integer' } & PickIfDefined> type ArrayBounds = Force<{ type: 'array', items: Returns } & PickIfDefined> -function applyTupleOptionality(xs: readonly unknown[], { min, max }: { min: number, max: number }): readonly unknown[] { +export function applyTupleOptionality(xs: readonly unknown[], { min, max }: { min: number, max: number }): readonly unknown[] { return min === max ? xs.map(getSchema) : [ ...xs.slice(0, min).map(getSchema), ...xs.slice(min).map(getSchema), diff --git a/packages/schema-to-string/src/exports.ts b/packages/schema-to-string/src/exports.ts index 479c525e..938c2ac5 100644 --- a/packages/schema-to-string/src/exports.ts +++ b/packages/schema-to-string/src/exports.ts @@ -1,2 +1,7 @@ export * as toString from './toString.js' +export { + callToString, + hasToString, + isShowable, +} from './shared.js' export { VERSION } from './version.js' diff --git a/packages/schema-to-string/src/shared.ts b/packages/schema-to-string/src/shared.ts new file mode 100644 index 00000000..6ad684b0 --- /dev/null +++ b/packages/schema-to-string/src/shared.ts @@ -0,0 +1,12 @@ + +export const hasToString = (x: unknown): x is { toString(): string } => + !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' + +export const isShowable = (u: unknown) => u == null + || typeof u === 'boolean' + || typeof u === 'number' + || typeof u === 'bigint' + || typeof u === 'string' + ; + +export function callToString(x: unknown): string { return hasToString(x) ? x.toString() : 'unknown' } diff --git a/packages/schema-to-string/src/toString.ts b/packages/schema-to-string/src/toString.ts index 6518d2ea..5ca45f7f 100644 --- a/packages/schema-to-string/src/toString.ts +++ b/packages/schema-to-string/src/toString.ts @@ -1,6 +1,7 @@ import type { Returns, Join, Showable, UnionToTuple } from '@traversable/registry' import { symbol } from '@traversable/registry' import { t } from '@traversable/schema' +import { isShowable, hasToString } from './shared.js' export { neverToString as never, @@ -32,22 +33,11 @@ const Symbol_optional: typeof symbol.optional = symbol.optional /** @internal */ const isArray = globalThis.Array.isArray -/** @internal */ -const hasToString = (x: unknown): x is { toString(): string } => - !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' - /** @internal */ const isOptional = (u: unknown): u is { toString(): T } => !!u && typeof u === 'function' && Symbol_optional in u && typeof u[Symbol_optional] === 'number' -/** @internal */ -const isShowable = (u: unknown) => u == null - || typeof u === 'boolean' - || typeof u === 'number' - || typeof u === 'bigint' - || typeof u === 'string' - /** @internal */ const stringify = (u: unknown) => typeof u === 'string' ? `'${u}'` : isShowable(u) ? globalThis.String(u) : 'string' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 326a5cd7..88e1b1eb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -272,6 +272,9 @@ importers: '@traversable/schema-to-json-schema': specifier: workspace:^ version: link:../schema-to-json-schema/dist + '@traversable/schema-to-string': + specifier: workspace:^ + version: link:../schema-to-string/dist publishDirectory: dist packages/schema-seed: From 9ad9a872c0795372dc8ab7d804b8011fe39d2559 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 9 Apr 2025 19:24:52 -0500 Subject: [PATCH 16/45] feat(generator): generates intersect schemas --- .../schema-generator/test/imports.test.ts | 11 +- .../test/test-data/array/equals.ts | 4 +- .../test/test-data/array/toJsonSchema.ts | 18 ++- .../test/test-data/intersect/core.ts | 42 +++++ .../test/test-data/intersect/equals.ts | 16 ++ .../test/test-data/intersect/extension.ts | 20 +++ .../test/test-data/intersect/toJsonSchema.ts | 20 +++ .../test/test-data/intersect/toString.ts | 13 ++ .../test/test-data/intersect/validate.ts | 21 +++ .../test/test-data/object/equals.ts | 19 +-- .../test/test-data/object/toJsonSchema.ts | 25 ++- .../test/test-data/object/validate.ts | 143 ++++++++---------- .../test/test-data/string/equals.ts | 4 +- .../test/test-data/tuple/core.ts | 19 +-- .../test/test-data/tuple/equals.ts | 3 +- .../test/test-data/tuple/toJsonSchema.ts | 43 ++++-- .../test/test-data/tuple/validate.ts | 6 +- .../test/test-data/union/core.ts | 53 +++---- .../test/test-data/union/equals.ts | 3 +- .../test/test-data/union/toJsonSchema.ts | 12 +- packages/schema/src/exports.ts | 6 + 21 files changed, 320 insertions(+), 181 deletions(-) create mode 100644 packages/schema-generator/test/test-data/intersect/core.ts create mode 100644 packages/schema-generator/test/test-data/intersect/equals.ts create mode 100644 packages/schema-generator/test/test-data/intersect/extension.ts create mode 100644 packages/schema-generator/test/test-data/intersect/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/intersect/toString.ts create mode 100644 packages/schema-generator/test/test-data/intersect/validate.ts diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index 499a1a2a..43cc1afa 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -34,6 +34,14 @@ let PATH = { toString: path.join(DATA_PATH, 'string', 'toString.ts'), validate: path.join(DATA_PATH, 'string', 'validate.ts'), }, + intersect: { + core: path.join(DATA_PATH, 'intersect', 'core.ts'), + extension: path.join(DATA_PATH, 'intersect', 'extension.ts'), + equals: path.join(DATA_PATH, 'intersect', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'intersect', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'intersect', 'toString.ts'), + validate: path.join(DATA_PATH, 'intersect', 'validate.ts'), + }, object: { core: path.join(DATA_PATH, 'object', 'core.ts'), extension: path.join(DATA_PATH, 'object', 'extension.ts'), @@ -61,10 +69,11 @@ let PATH = { }, targets: { array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), + intersect: path.join(DIR_PATH, '__generated__', 'intersect.gen.ts'), object: path.join(DIR_PATH, '__generated__', 'object.gen.ts'), + string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), tuple: path.join(DIR_PATH, '__generated__', 'tuple.gen.ts'), union: path.join(DIR_PATH, '__generated__', 'union.gen.ts'), - string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), } } diff --git a/packages/schema-generator/test/test-data/array/equals.ts b/packages/schema-generator/test/test-data/array/equals.ts index 39e977d8..75114960 100644 --- a/packages/schema-generator/test/test-data/array/equals.ts +++ b/packages/schema-generator/test/test-data/array/equals.ts @@ -3,11 +3,12 @@ import { has, Array_isArray, Object_is } from '@traversable/registry' import type { t } from '@traversable/schema' export type equals = never | T.Equal + export function equals>(arraySchema: S): equals export function equals(arraySchema: S): equals export function equals({ def: { def } }: { def: { def: unknown } }): T.Equal { let equals = has('equals', (x): x is T.Equal => typeof x === 'function')(def) ? def.equals : Object_is - return (l, r) => { + function arrayEquals(l: unknown[], r: unknown[]): boolean { if (Object_is(l, r)) return true if (Array_isArray(l)) { if (!Array_isArray(r)) return false @@ -18,4 +19,5 @@ export function equals({ def: { def } }: { def: { def: unknown } }): T.Equal { return true } else return false } + return arrayEquals } diff --git a/packages/schema-generator/test/test-data/array/toJsonSchema.ts b/packages/schema-generator/test/test-data/array/toJsonSchema.ts index 2d20d7ac..5d0db639 100644 --- a/packages/schema-generator/test/test-data/array/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/array/toJsonSchema.ts @@ -3,12 +3,15 @@ import type * as T from '@traversable/registry' import { has } from '@traversable/registry' import type { SizeBounds } from '@traversable/schema-to-json-schema' -export type toJsonSchema = never | T.Force< - & { type: 'array', items: T.Returns } - & T.PickIfDefined -> -export function toJsonSchema>(arraySchema: T): () => toJsonSchema -export function toJsonSchema(arraySchema: T): () => toJsonSchema +export interface toJsonSchema { + (): never | T.Force< + & { type: 'array', items: T.Returns } + & T.PickIfDefined + > +} + +export function toJsonSchema>(arraySchema: T): toJsonSchema +export function toJsonSchema(arraySchema: T): toJsonSchema export function toJsonSchema( { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, ): () => { @@ -17,7 +20,7 @@ export function toJsonSchema( minLength?: number maxLength?: number } { - return () => { + function arrayToJsonSchema() { let items = has('toJsonSchema', (x) => typeof x === 'function')(def) ? def.toJsonSchema() : def let out = { type: 'array' as const, @@ -29,4 +32,5 @@ export function toJsonSchema( if (typeof maxLength !== 'number') delete out.maxLength return out } + return arrayToJsonSchema } diff --git a/packages/schema-generator/test/test-data/intersect/core.ts b/packages/schema-generator/test/test-data/intersect/core.ts new file mode 100644 index 00000000..a6363ae4 --- /dev/null +++ b/packages/schema-generator/test/test-data/intersect/core.ts @@ -0,0 +1,42 @@ +import { + bindUserDefinitions, + Object_assign, + URI +} from '@traversable/registry' + +import { + t, + Predicate, +} from '@traversable/schema' + +export interface union extends union.core { + //<%= types %> +} + +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: S): {} { return union.def(schemas) } +export namespace union { + export let proto = {} as union + export function def(xs: T): union + export function def(xs: T): {} { + let userDefinitions = { + //<%= terms %> + } + const anyOf = xs.every(t.isPredicate) ? Predicate.is.union(xs) : Predicate.is.unknown + function UnionSchema(src: unknown) { return anyOf(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.proto) + return Object_assign(UnionSchema, bindUserDefinitions(UnionSchema, userDefinitions)) + } +} +export declare namespace union { + interface core { + (u: unknown): u is this['_type'] + tag: URI.union + def: S + _type: S[number & keyof S]['_type' & keyof S[number & keyof S]] + } + type type = never | T +} diff --git a/packages/schema-generator/test/test-data/intersect/equals.ts b/packages/schema-generator/test/test-data/intersect/equals.ts new file mode 100644 index 00000000..316a4fa0 --- /dev/null +++ b/packages/schema-generator/test/test-data/intersect/equals.ts @@ -0,0 +1,16 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema' + +export type equals = Equal +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals({ def }: t.intersect<{ equals: Equal }[]>): Equal { + function intersectEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (!def[ix].equals(l, r)) return false + return true + } + return intersectEquals +} diff --git a/packages/schema-generator/test/test-data/intersect/extension.ts b/packages/schema-generator/test/test-data/intersect/extension.ts new file mode 100644 index 00000000..a78cd352 --- /dev/null +++ b/packages/schema-generator/test/test-data/intersect/extension.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Extension { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let extension = { + equals, + toJsonSchema, + toString, + validate, +} + +declare const xs: Extension diff --git a/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts b/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts new file mode 100644 index 00000000..0c09ed76 --- /dev/null +++ b/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts @@ -0,0 +1,20 @@ +import type { Returns } from '@traversable/registry' +import { t } from '@traversable/schema' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + allOf: { [I in keyof S]: Returns } + } +} + +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema({ def }: t.intersect): () => {} { + function intersectToJsonSchema() { + return { + allOf: def.map(getSchema) + } + } + return intersectToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/intersect/toString.ts b/packages/schema-generator/test/test-data/intersect/toString.ts new file mode 100644 index 00000000..061dfc65 --- /dev/null +++ b/packages/schema-generator/test/test-data/intersect/toString.ts @@ -0,0 +1,13 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import { t } from '@traversable/schema' +import { callToString } from '@traversable/schema-to-string' + +export type toString = never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: ReturnType }, ' & '>})` + +export function toString(intersectSchema: t.intersect): toString +export function toString({ def }: t.intersect): string { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' +} diff --git a/packages/schema-generator/test/test-data/intersect/validate.ts b/packages/schema-generator/test/test-data/intersect/validate.ts new file mode 100644 index 00000000..6237a2da --- /dev/null +++ b/packages/schema-generator/test/test-data/intersect/validate.ts @@ -0,0 +1,21 @@ +import { URI } from '@traversable/registry' +import { t } from '@traversable/schema' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(intersectSchema: t.intersect): validate +export function validate(intersectSchema: t.intersect): validate +export function validate({ def }: t.intersect) { + validateIntersect.tag = URI.intersect + function validateIntersect(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results !== true) + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + return errors.length === 0 || errors + } + return validateIntersect +} diff --git a/packages/schema-generator/test/test-data/object/equals.ts b/packages/schema-generator/test/test-data/object/equals.ts index c340a938..3af35c15 100644 --- a/packages/schema-generator/test/test-data/object/equals.ts +++ b/packages/schema-generator/test/test-data/object/equals.ts @@ -1,32 +1,29 @@ import type * as T from '@traversable/registry' -import { - Array_isArray, - Object_hasOwn, - Object_is, -} from '@traversable/registry' +import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' import type { t } from '@traversable/schema' export type equals = never | T.Equal export function equals(objectSchema: t.object): equals> export function equals(objectSchema: t.object): equals> -export function equals(objectSchema: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { - return (l, r) => { +export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { + function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { if (Object_is(l, r)) return true if (!l || typeof l !== 'object' || Array_isArray(l)) return false if (!r || typeof r !== 'object' || Array_isArray(r)) return false - for (const k in objectSchema.def) { + for (const k in def) { const lHas = Object_hasOwn(l, k) const rHas = Object_hasOwn(r, k) if (lHas) { if (!rHas) return false - if (!objectSchema.def[k].equals(l[k], r[k])) return false + if (!def[k].equals(l[k], r[k])) return false } if (rHas) { if (!lHas) return false - if (!objectSchema.def[k].equals(l[k], r[k])) return false + if (!def[k].equals(l[k], r[k])) return false } - if (!objectSchema.def[k].equals(l[k], r[k])) return false + if (!def[k].equals(l[k], r[k])) return false } return true } + return objectEquals } diff --git a/packages/schema-generator/test/test-data/object/toJsonSchema.ts b/packages/schema-generator/test/test-data/object/toJsonSchema.ts index 4a6b8d93..2e5ac646 100644 --- a/packages/schema-generator/test/test-data/object/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/object/toJsonSchema.ts @@ -5,24 +5,23 @@ import { isRequired, property } from '@traversable/schema-to-json-schema' import { t } from '@traversable/schema' export interface toJsonSchema = RequiredKeys> { - toJsonSchema(): { + (): { type: 'object' required: { [I in keyof KS]: KS[I] & string } properties: { [K in keyof T]: Returns } } } -export function toJsonSchema(objectSchema: S): toJsonSchema -export function toJsonSchema>(objectSchema: T): { - type: 'object' - required: { [I in keyof KS]: KS[I] & string } - properties: { [K in keyof T]: Returns } -} -export function toJsonSchema({ def }: { def: { [x: string]: unknown } }) { +export function toJsonSchema>(objectSchema: t.object): toJsonSchema +export function toJsonSchema>(objectSchema: t.object): toJsonSchema +export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { const required = Object_keys(def).filter(isRequired(def)) - return { - type: 'object', - required, - properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), - } as never + function objectToJsonSchema() { + return { + type: 'object' as const, + required, + properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), + } + } + return objectToJsonSchema } diff --git a/packages/schema-generator/test/test-data/object/validate.ts b/packages/schema-generator/test/test-data/object/validate.ts index 0cc1a718..2b806108 100644 --- a/packages/schema-generator/test/test-data/object/validate.ts +++ b/packages/schema-generator/test/test-data/object/validate.ts @@ -1,4 +1,3 @@ -import type { Unknown } from '@traversable/registry' import { Array_isArray, Object_keys, @@ -21,98 +20,88 @@ let isKeyOf = (k: keyof any, u: T): k is keyof T => /** @internal */ let isOptional = t.has('tag', t.eq(URI.optional)) -validate.tag = URI.object export type validate = never | ValidationFn -export function validate( - objectSchema: t.object, - u: t.object['_type'] | Unknown, - path?: (keyof any)[] -): true | ValidationError[] - -export function validate( - objectSchema: t.object, - u: t.object['_type'] | Unknown, - path?: (keyof any)[] -): true | ValidationError[] - -export function validate( - objectSchema: t.object, - u: unknown, - path: (keyof any)[] = [] -): true | ValidationError[] { - if (!isObject(u)) return [Errors.object(u, path)] - let errors = Array.of() - let { schema: { optionalTreatment } } = getConfig() - let keys = Object_keys(objectSchema.def) - if (optionalTreatment === 'exactOptional') { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path_ = [...path, k] - if (Object_hasOwn(u, k) && u[k] === undefined) { - if (isOptional(objectSchema.def[k].validate)) { +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { + validateObject.tag = URI.object + function validateObject(u: unknown, path: (keyof any)[] = []) { + if (!isObject(u)) return [Errors.object(u, path)] + let errors = Array.of() + let { schema: { optionalTreatment } } = getConfig() + let keys = Object_keys(objectSchema.def) + if (optionalTreatment === 'exactOptional') { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path_ = [...path, k] + if (Object_hasOwn(u, k) && u[k] === undefined) { + if (isOptional(objectSchema.def[k].validate)) { + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + let args = [u[k], path_, tag] as never as [unknown, (keyof any)[]] + errors.push(NullaryErrors[tag](...args)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path_)) + } + } + let results = objectSchema.def[k].validate(u[k], path_) + if (results === true) continue let tag = typeName(objectSchema.def[k].validate) if (isKeyOf(tag, NullaryErrors)) { - let args = [u[k], path_, tag] as never as [unknown, (keyof any)[]] - errors.push(NullaryErrors[tag](...args)) + errors.push(NullaryErrors[tag](u[k], path_, tag)) } else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path_)) + errors.push(UnaryErrors[tag].invalid(u[k], path_)) } + errors.push(...results) } - let results = objectSchema.def[k].validate(u[k], path_) - if (results === true) continue - let tag = typeName(objectSchema.def[k].validate) - if (isKeyOf(tag, NullaryErrors)) { - errors.push(NullaryErrors[tag](u[k], path_, tag)) - } - else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag].invalid(u[k], path_)) - } - errors.push(...results) - } - else if (Object_hasOwn(u, k)) { - let results = objectSchema.def[k].validate(u[k], path_) - if (results === true) continue - errors.push(...results) - continue - } else { - errors.push(UnaryErrors.object.missing(u, path_)) - continue - } - } - } - else { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path_ = [...path, k] - if (!Object_hasOwn(u, k)) { - if (!isOptional(objectSchema.def[k].validate)) { + else if (Object_hasOwn(u, k)) { + let results = objectSchema.def[k].validate(u[k], path_) + if (results === true) continue + errors.push(...results) + continue + } else { errors.push(UnaryErrors.object.missing(u, path_)) continue } - else { - if (!Object_hasOwn(u, k)) continue - if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { - if (u[k] === undefined) continue - let results = objectSchema.def[k].validate(u[k], path_) - if (results === true) continue - for (let j = 0; j < results.length; j++) { - let result = results[j] - errors.push(result) - continue + } + } + else { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path_ = [...path, k] + if (!Object_hasOwn(u, k)) { + if (!isOptional(objectSchema.def[k].validate)) { + errors.push(UnaryErrors.object.missing(u, path_)) + continue + } + else { + if (!Object_hasOwn(u, k)) continue + if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { + if (u[k] === undefined) continue + let results = objectSchema.def[k].validate(u[k], path_) + if (results === true) continue + for (let j = 0; j < results.length; j++) { + let result = results[j] + errors.push(result) + continue + } } } } - } - let results = objectSchema.def[k].validate(u[k], path_) - if (results === true) continue - for (let l = 0; l < results.length; l++) { - let result = results[l] - errors.push(result) + let results = objectSchema.def[k].validate(u[k], path_) + if (results === true) continue + for (let l = 0; l < results.length; l++) { + let result = results[l] + errors.push(result) + } } } + return errors.length === 0 || errors } - return errors.length === 0 || errors + + return validateObject } diff --git a/packages/schema-generator/test/test-data/string/equals.ts b/packages/schema-generator/test/test-data/string/equals.ts index 758a2ab6..b05534ca 100644 --- a/packages/schema-generator/test/test-data/string/equals.ts +++ b/packages/schema-generator/test/test-data/string/equals.ts @@ -1 +1,3 @@ -export function equals(left: string, right: string): boolean { return left === right } +export function equals(left: string, right: string): boolean { + return left === right +} diff --git a/packages/schema-generator/test/test-data/tuple/core.ts b/packages/schema-generator/test/test-data/tuple/core.ts index 0d7b8231..5e19d221 100644 --- a/packages/schema-generator/test/test-data/tuple/core.ts +++ b/packages/schema-generator/test/test-data/tuple/core.ts @@ -13,7 +13,8 @@ import { } from '@traversable/registry' import type { - Label, + FirstOptionalItem, + TupleType, ValidateTuple, } from '@traversable/schema' import { @@ -22,22 +23,6 @@ import { } from '@traversable/schema' -export type FirstOptionalItem - = S extends readonly [infer H, ...infer T] ? t.optional extends H ? Offset['length'] : FirstOptionalItem : never - ; - -export type TupleType = never - | t.optional extends T[number & keyof T] - ? T extends readonly [infer Head, ...infer Tail] - ? [Head] extends [t.optional] ? Label< - { [ix in keyof Out]: Out[ix]['_type' & keyof Out[ix]] }, - { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } - > - : TupleType - : never - : { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } - ; - const replaceBoolean = replaceBooleanConstructor(t.nonnullable) interface tuple extends tuple.core { diff --git a/packages/schema-generator/test/test-data/tuple/equals.ts b/packages/schema-generator/test/test-data/tuple/equals.ts index 6e0adac4..ed1a055f 100644 --- a/packages/schema-generator/test/test-data/tuple/equals.ts +++ b/packages/schema-generator/test/test-data/tuple/equals.ts @@ -7,7 +7,7 @@ export type equals = Equal export function equals(tupleSchema: t.tuple): equals export function equals(tupleSchema: t.tuple): equals export function equals(tupleSchema: t.tuple) { - return (l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean => { + function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { if (Object_is(l, r)) return true if (Array_isArray(l)) { if (!Array_isArray(r)) return false @@ -23,4 +23,5 @@ export function equals(tupleSchema: t.tuple) { } return false } + return tupleEquals } diff --git a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts index 8e7ea956..88e2190e 100644 --- a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts @@ -1,25 +1,38 @@ import type { Returns } from '@traversable/registry' +import type { t } from '@traversable/schema' import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' import type { MinItems } from '@traversable/schema-to-json-schema' -import type { t } from '@traversable/schema' export interface toJsonSchema { - type: 'array', - items: { [I in keyof T]: Returns } - additionalItems: false - minItems: MinItems - maxItems: T['length' & keyof T] + (): { + type: 'array', + items: { [I in keyof T]: Returns } + additionalItems: false + minItems: MinItems + maxItems: T['length' & keyof T] + } } export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema -export function toJsonSchema({ def }: t.tuple) { - const min = minItems(def) - const max = def.length - return { - type: 'array' as const, - additionalItems: false as const, - items: applyTupleOptionality(def, { min, max }) as never, - minItems: min as never, - maxItems: max, +export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema +export function toJsonSchema({ def }: t.tuple): () => { + type: 'array' + items: unknown + additionalItems: false + minItems?: number + maxItems?: number +} { + let min = minItems(def) + let max = def.length + let items = applyTupleOptionality(def, { min, max }) + function tupleToJsonSchema() { + return { + type: 'array' as const, + additionalItems: false as const, + items, + minItems: min, + maxItems: max, + } } + return tupleToJsonSchema } diff --git a/packages/schema-generator/test/test-data/tuple/validate.ts b/packages/schema-generator/test/test-data/tuple/validate.ts index 875c1dbb..68c59438 100644 --- a/packages/schema-generator/test/test-data/tuple/validate.ts +++ b/packages/schema-generator/test/test-data/tuple/validate.ts @@ -3,13 +3,12 @@ import { t } from '@traversable/schema' import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' import { Errors } from '@traversable/derive-validators' -validate.tag = URI.tuple - export type validate = Validate export function validate(tupleSchema: t.tuple<[...S]>): validate export function validate(tupleSchema: t.tuple<[...S]>): validate export function validate(tupleSchema: t.tuple<[...S]>): Validate { - return (u, path = []) => { + validateTuple.tag = URI.tuple + function validateTuple(u: unknown, path: (keyof any)[] = []) { let errors = Array.of() if (!Array_isArray(u)) return [Errors.array(u, path)] for (let i = 0; i < tupleSchema.def.length; i++) { @@ -31,4 +30,5 @@ export function validate(tupleSchema: t.tuple<[. } return errors.length === 0 || errors } + return validateTuple } diff --git a/packages/schema-generator/test/test-data/union/core.ts b/packages/schema-generator/test/test-data/union/core.ts index a6363ae4..75371200 100644 --- a/packages/schema-generator/test/test-data/union/core.ts +++ b/packages/schema-generator/test/test-data/union/core.ts @@ -1,42 +1,39 @@ -import { - bindUserDefinitions, - Object_assign, - URI -} from '@traversable/registry' +import { bindUserDefinitions, Object_assign, URI } from '@traversable/registry' +import type { IntersectType } from '@traversable/schema' +import { t, Predicate } from '@traversable/schema' -import { - t, - Predicate, -} from '@traversable/schema' - -export interface union extends union.core { +export interface intersect extends intersect.core { //<%= types %> } -export function union(...schemas: S): union -export function union }>(...schemas: S): union -export function union(...schemas: S): {} { return union.def(schemas) } -export namespace union { - export let proto = {} as union - export function def(xs: T): union - export function def(xs: T): {} { +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: S) { + return intersect.def(schemas) +} + +export namespace intersect { + export let proto = {} as intersect + export function def(xs: readonly [...T]): intersect + export function def(xs: readonly [...T]): {} { let userDefinitions = { //<%= terms %> } - const anyOf = xs.every(t.isPredicate) ? Predicate.is.union(xs) : Predicate.is.unknown - function UnionSchema(src: unknown) { return anyOf(src) } - UnionSchema.tag = URI.union - UnionSchema.def = xs - Object_assign(UnionSchema, union.proto) - return Object_assign(UnionSchema, bindUserDefinitions(UnionSchema, userDefinitions)) + const allOf = xs.every(t.isPredicate) ? Predicate.is.intersect(xs) : Predicate.is.unknown + function IntersectSchema(src: unknown) { return allOf(src) } + IntersectSchema.tag = URI.intersect + IntersectSchema.def = xs + Object_assign(IntersectSchema, intersect.proto) + return Object_assign(IntersectSchema, bindUserDefinitions(IntersectSchema, userDefinitions)) } } -export declare namespace union { + +export declare namespace intersect { interface core { (u: unknown): u is this['_type'] - tag: URI.union + tag: URI.intersect def: S - _type: S[number & keyof S]['_type' & keyof S[number & keyof S]] + _type: IntersectType } - type type = never | T + type type> = never | T } diff --git a/packages/schema-generator/test/test-data/union/equals.ts b/packages/schema-generator/test/test-data/union/equals.ts index 5233d0be..ae0a6e92 100644 --- a/packages/schema-generator/test/test-data/union/equals.ts +++ b/packages/schema-generator/test/test-data/union/equals.ts @@ -6,10 +6,11 @@ export type equals = Equal export function equals(unionSchema: t.union<[...S]>): equals export function equals(unionSchema: t.union<[...S]>): equals export function equals({ def }: t.union<{ equals: Equal }[]>): Equal { - return (l: unknown, r: unknown): boolean => { + function unionEquals(l: unknown, r: unknown): boolean { if (Object_is(l, r)) return true for (let ix = def.length; ix-- !== 0;) if (def[ix].equals(l, r)) return true return false } + return unionEquals } diff --git a/packages/schema-generator/test/test-data/union/toJsonSchema.ts b/packages/schema-generator/test/test-data/union/toJsonSchema.ts index d578fc98..90fe2152 100644 --- a/packages/schema-generator/test/test-data/union/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/union/toJsonSchema.ts @@ -2,14 +2,16 @@ import type { Returns } from '@traversable/registry' import { t } from '@traversable/schema' import { getSchema } from '@traversable/schema-to-json-schema' -export type toJsonSchema = { - anyOf: { [I in keyof S]: Returns } +export interface toJsonSchema { + (): { anyOf: { [I in keyof S]: Returns } } } export function toJsonSchema(unionSchema: t.union): toJsonSchema export function toJsonSchema(unionSchema: t.union): toJsonSchema -export function toJsonSchema({ def }: t.union): {} { - return { - anyOf: def.map(getSchema) +export function toJsonSchema({ def }: t.union): () => {} { + return function unionToJsonSchema() { + return { + anyOf: def.map(getSchema) + } } } diff --git a/packages/schema/src/exports.ts b/packages/schema/src/exports.ts index 5d29b65d..12edfbe4 100644 --- a/packages/schema/src/exports.ts +++ b/packages/schema/src/exports.ts @@ -76,12 +76,18 @@ export { get, get$ } from './utils.js' export { VERSION } from './version.js' +export type { + FirstOptionalItem, + IntersectType, + TupleType, +} from './schema.js' export { /** @internal */ replaceBooleanConstructor as __replaceBooleanConstructor, /** @internal */ carryover as __carryover, } from './schema.js' + export { /** @internal */ within as __within, From 54ce7b171828433c99330e1897e0ee31d1c460a5 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 9 Apr 2025 21:10:10 -0500 Subject: [PATCH 17/45] fix(generator): gets the types right for `.toJsonSchema` and `.toString` --- .../schema-generator/test/generated.test.ts | 18 +++ .../schema-generator/test/imports.test.ts | 9 ++ .../test/test-data/intersect/core.ts | 52 ++++---- .../test/test-data/intersect/toJsonSchema.ts | 4 +- .../test/test-data/intersect/toString.ts | 15 ++- .../test/test-data/number/core.ts | 121 ++++++++++++++++++ .../test/test-data/number/equals.ts | 6 + .../test/test-data/number/extension.ts | 18 +++ .../test/test-data/number/toJsonSchema.ts | 23 ++++ .../test/test-data/number/toString.ts | 2 + .../test/test-data/number/validate.ts | 13 ++ .../test/test-data/object/toJsonSchema.ts | 6 +- .../test/test-data/object/toString.ts | 12 +- .../test/test-data/string/equals.ts | 3 + .../test/test-data/string/extension.ts | 20 ++- .../test/test-data/string/toJsonSchema.ts | 24 ++-- .../test/test-data/string/toString.ts | 1 + .../test/test-data/string/validate.ts | 14 +- .../test/test-data/tuple/toJsonSchema.ts | 23 +++- .../test/test-data/tuple/toString.ts | 31 +++-- .../test/test-data/union/core.ts | 53 ++++---- .../test/test-data/union/toJsonSchema.ts | 4 +- .../test/test-data/union/toString.ts | 15 ++- packages/schema-to-json-schema/src/exports.ts | 6 +- 24 files changed, 376 insertions(+), 117 deletions(-) create mode 100644 packages/schema-generator/test/generated.test.ts create mode 100644 packages/schema-generator/test/test-data/number/core.ts create mode 100644 packages/schema-generator/test/test-data/number/equals.ts create mode 100644 packages/schema-generator/test/test-data/number/extension.ts create mode 100644 packages/schema-generator/test/test-data/number/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/number/toString.ts create mode 100644 packages/schema-generator/test/test-data/number/validate.ts diff --git a/packages/schema-generator/test/generated.test.ts b/packages/schema-generator/test/generated.test.ts new file mode 100644 index 00000000..94385d59 --- /dev/null +++ b/packages/schema-generator/test/generated.test.ts @@ -0,0 +1,18 @@ +import { tuple } from './__generated__/tuple.gen.js' +import { string } from './__generated__/string.gen.js' +import { number } from './__generated__/number.gen.js' +import { object } from './__generated__/object.gen.js' +import { intersect } from './__generated__/intersect.gen.js' +import { union } from './__generated__/union.gen.js' + +let xs = intersect(string).toJsonSchema() +let ys = tuple(string).toJsonSchema() +let zs = object({ a: number }).toJsonSchema() +let as = object({ a: string }).toJsonSchema() +let bs = union(string).toJsonSchema() + +let cs = intersect(string, number).toString() +let ds = tuple(string).toString() +let es = object({ a: number }).toString() +let gs = union(string, number).toString() +let hs = object({ a: object({ b: tuple(number, intersect(object({ c: string }), object({ d: union(number, string) }))) }) }).toString() diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index 43cc1afa..84960a86 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -34,6 +34,14 @@ let PATH = { toString: path.join(DATA_PATH, 'string', 'toString.ts'), validate: path.join(DATA_PATH, 'string', 'validate.ts'), }, + number: { + core: path.join(DATA_PATH, 'number', 'core.ts'), + extension: path.join(DATA_PATH, 'number', 'extension.ts'), + equals: path.join(DATA_PATH, 'number', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'number', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'number', 'toString.ts'), + validate: path.join(DATA_PATH, 'number', 'validate.ts'), + }, intersect: { core: path.join(DATA_PATH, 'intersect', 'core.ts'), extension: path.join(DATA_PATH, 'intersect', 'extension.ts'), @@ -70,6 +78,7 @@ let PATH = { targets: { array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), intersect: path.join(DIR_PATH, '__generated__', 'intersect.gen.ts'), + number: path.join(DIR_PATH, '__generated__', 'number.gen.ts'), object: path.join(DIR_PATH, '__generated__', 'object.gen.ts'), string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), tuple: path.join(DIR_PATH, '__generated__', 'tuple.gen.ts'), diff --git a/packages/schema-generator/test/test-data/intersect/core.ts b/packages/schema-generator/test/test-data/intersect/core.ts index a6363ae4..5dc88b73 100644 --- a/packages/schema-generator/test/test-data/intersect/core.ts +++ b/packages/schema-generator/test/test-data/intersect/core.ts @@ -1,42 +1,40 @@ -import { - bindUserDefinitions, - Object_assign, - URI -} from '@traversable/registry' -import { - t, - Predicate, -} from '@traversable/schema' +import { bindUserDefinitions, Object_assign, URI } from '@traversable/registry' +import type { IntersectType } from '@traversable/schema' +import { t, Predicate } from '@traversable/schema' -export interface union extends union.core { +export interface intersect extends intersect.core { //<%= types %> } -export function union(...schemas: S): union -export function union }>(...schemas: S): union -export function union(...schemas: S): {} { return union.def(schemas) } -export namespace union { - export let proto = {} as union - export function def(xs: T): union - export function def(xs: T): {} { +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: S) { + return intersect.def(schemas) +} + +export namespace intersect { + export let proto = {} as intersect + export function def(xs: readonly [...T]): intersect + export function def(xs: readonly [...T]): {} { let userDefinitions = { //<%= terms %> } - const anyOf = xs.every(t.isPredicate) ? Predicate.is.union(xs) : Predicate.is.unknown - function UnionSchema(src: unknown) { return anyOf(src) } - UnionSchema.tag = URI.union - UnionSchema.def = xs - Object_assign(UnionSchema, union.proto) - return Object_assign(UnionSchema, bindUserDefinitions(UnionSchema, userDefinitions)) + const allOf = xs.every(t.isPredicate) ? Predicate.is.intersect(xs) : Predicate.is.unknown + function IntersectSchema(src: unknown) { return allOf(src) } + IntersectSchema.tag = URI.intersect + IntersectSchema.def = xs + Object_assign(IntersectSchema, intersect.proto) + return Object_assign(IntersectSchema, bindUserDefinitions(IntersectSchema, userDefinitions)) } } -export declare namespace union { + +export declare namespace intersect { interface core { (u: unknown): u is this['_type'] - tag: URI.union + tag: URI.intersect def: S - _type: S[number & keyof S]['_type' & keyof S[number & keyof S]] + _type: IntersectType } - type type = never | T + type type> = never | T } diff --git a/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts b/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts index 0c09ed76..4f2d1c95 100644 --- a/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts @@ -2,9 +2,9 @@ import type { Returns } from '@traversable/registry' import { t } from '@traversable/schema' import { getSchema } from '@traversable/schema-to-json-schema' -export interface toJsonSchema { +export interface toJsonSchema { (): { - allOf: { [I in keyof S]: Returns } + allOf: { [I in keyof T]: Returns } } } diff --git a/packages/schema-generator/test/test-data/intersect/toString.ts b/packages/schema-generator/test/test-data/intersect/toString.ts index 061dfc65..d1facc45 100644 --- a/packages/schema-generator/test/test-data/intersect/toString.ts +++ b/packages/schema-generator/test/test-data/intersect/toString.ts @@ -3,11 +3,16 @@ import { Array_isArray } from '@traversable/registry' import { t } from '@traversable/schema' import { callToString } from '@traversable/schema-to-string' -export type toString = never | [T] extends [readonly []] ? 'never' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: ReturnType }, ' & '>})` +export interface toString { + (): never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` +} export function toString(intersectSchema: t.intersect): toString -export function toString({ def }: t.intersect): string { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' +export function toString({ def }: t.intersect): () => string { + function intersectToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' + } + return intersectToString } diff --git a/packages/schema-generator/test/test-data/number/core.ts b/packages/schema-generator/test/test-data/number/core.ts new file mode 100644 index 00000000..75468d9c --- /dev/null +++ b/packages/schema-generator/test/test-data/number/core.ts @@ -0,0 +1,121 @@ +import { Math_min, Math_max, Object_assign, URI } from '@traversable/registry' + +import type { Bounds } from '@traversable/schema' +import { __carryover as carryover, __within as within } from '@traversable/schema' + +export let userDefinitions = { + //<%= terms %> +} + +export { number_ as number } + +interface number_ extends number_.core { + //<%= types %> +} + +const number_ = Object_assign( + function NumberSchema(src: unknown) { return typeof src === 'number' }, + userDefinitions, +) as number_ + +number_.tag = URI.number +number_.def = 0 +number_.min = function numberMin(minimum) { + return Object_assign( + boundedNumber({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +number_.max = function numberMax(maximum) { + return Object_assign( + boundedNumber({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +number_.moreThan = function numberMoreThan(exclusiveMinimum) { + return Object_assign( + boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), + { exclusiveMinimum }, + ) +} +number_.lessThan = function numberLessThan(exclusiveMaximum) { + return Object_assign( + boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), + { exclusiveMaximum }, + ) +} +number_.between = function numberBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedNumber({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +declare namespace number_ { + interface core extends number_.methods { + (u: unknown): u is number + _type: number + tag: URI.number + def: this['_type'] + minimum?: number + maximum?: number + exclusiveMinimum?: number + exclusiveMaximum?: number + } + interface methods { + min(minimum: Min): number_.Min + max(maximum: Max): number_.Max + moreThan(moreThan: Min): ExclusiveMin + lessThan(lessThan: Max): ExclusiveMax + between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.between<[min: X, max: Self['maximum']]> + : number_.min + ; + type Max + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.between<[min: Self['minimum'], max: X]> + : number_.max + ; + type ExclusiveMin + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.maxStrictMin<[min: X, Self['maximum']]> + : number_.moreThan + ; + type ExclusiveMax + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.minStrictMax<[Self['minimum'], min: X]> + : number_.lessThan + ; + interface min extends number_ { minimum: Min } + interface max extends number_ { maximum: Max } + interface moreThan extends number_ { exclusiveMinimum: Min } + interface lessThan extends number_ { exclusiveMaximum: Max } + interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } + interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } + interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } + interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } +} + +function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedNumberSchema(u: unknown) { + return typeof u === 'number' && within(bounds)(u) + }, carry, number_) +} diff --git a/packages/schema-generator/test/test-data/number/equals.ts b/packages/schema-generator/test/test-data/number/equals.ts new file mode 100644 index 00000000..15704197 --- /dev/null +++ b/packages/schema-generator/test/test-data/number/equals.ts @@ -0,0 +1,6 @@ +import { Equal } from "@traversable/registry" + +export type equals = Equal +export function equals(left: number, right: number): boolean { + return Equal.SameValueNumber(left, right) +} diff --git a/packages/schema-generator/test/test-data/number/extension.ts b/packages/schema-generator/test/test-data/number/extension.ts new file mode 100644 index 00000000..a3c0f99f --- /dev/null +++ b/packages/schema-generator/test/test-data/number/extension.ts @@ -0,0 +1,18 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Extension { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let extension = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/test/test-data/number/toJsonSchema.ts b/packages/schema-generator/test/test-data/number/toJsonSchema.ts new file mode 100644 index 00000000..371ea7c1 --- /dev/null +++ b/packages/schema-generator/test/test-data/number/toJsonSchema.ts @@ -0,0 +1,23 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '@traversable/schema' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } + +export function toJsonSchema(schema: t.number): toJsonSchema +export function toJsonSchema(schema: t.number): toJsonSchema { + function numberToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'number' as const, + ...bounds, + } + } + return numberToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/number/toString.ts b/packages/schema-generator/test/test-data/number/toString.ts new file mode 100644 index 00000000..912565e6 --- /dev/null +++ b/packages/schema-generator/test/test-data/number/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } diff --git a/packages/schema-generator/test/test-data/number/validate.ts b/packages/schema-generator/test/test-data/number/validate.ts new file mode 100644 index 00000000..cb1d5cb0 --- /dev/null +++ b/packages/schema-generator/test/test-data/number/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(numberSchema: S): validate { + validateNumber.tag = URI.number + function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return numberSchema(u) || [NullaryErrors.number(u, path)] + } + return validateNumber +} diff --git a/packages/schema-generator/test/test-data/object/toJsonSchema.ts b/packages/schema-generator/test/test-data/object/toJsonSchema.ts index 2e5ac646..90d08777 100644 --- a/packages/schema-generator/test/test-data/object/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/object/toJsonSchema.ts @@ -4,7 +4,7 @@ import type { RequiredKeys } from '@traversable/schema-to-json-schema' import { isRequired, property } from '@traversable/schema-to-json-schema' import { t } from '@traversable/schema' -export interface toJsonSchema = RequiredKeys> { +export interface toJsonSchema = RequiredKeys> { (): { type: 'object' required: { [I in keyof KS]: KS[I] & string } @@ -12,8 +12,8 @@ export interface toJsonSchema = RequiredKeys> { } } -export function toJsonSchema>(objectSchema: t.object): toJsonSchema -export function toJsonSchema>(objectSchema: t.object): toJsonSchema +export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema(objectSchema: t.object): toJsonSchema export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { const required = Object_keys(def).filter(isRequired(def)) function objectToJsonSchema() { diff --git a/packages/schema-generator/test/test-data/object/toString.ts b/packages/schema-generator/test/test-data/object/toString.ts index 47e2afe7..9e8680b1 100644 --- a/packages/schema-generator/test/test-data/object/toString.ts +++ b/packages/schema-generator/test/test-data/object/toString.ts @@ -16,13 +16,15 @@ const hasOptionalSymbol = (u: unknown): u is { toString(): T } => const hasToString = (x: unknown): x is { toString(): string } => !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' -export type toString> = never - | [keyof T] extends [never] ? '{}' - /* @ts-expect-error */ - : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` +export interface toString> { + (): never + | [keyof T] extends [never] ? '{}' + /* @ts-expect-error */ + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` +} -export function toString>(objectSchema: t.object): toString +export function toString>(objectSchema: t.object): toString export function toString({ def }: t.object) { if (!!def && typeof def === 'object') { const entries = Object.entries(def) diff --git a/packages/schema-generator/test/test-data/string/equals.ts b/packages/schema-generator/test/test-data/string/equals.ts index b05534ca..b9444108 100644 --- a/packages/schema-generator/test/test-data/string/equals.ts +++ b/packages/schema-generator/test/test-data/string/equals.ts @@ -1,3 +1,6 @@ +import type { Equal } from '@traversable/registry' + +export type equals = Equal export function equals(left: string, right: string): boolean { return left === right } diff --git a/packages/schema-generator/test/test-data/string/extension.ts b/packages/schema-generator/test/test-data/string/extension.ts index 80eff013..3a592518 100644 --- a/packages/schema-generator/test/test-data/string/extension.ts +++ b/packages/schema-generator/test/test-data/string/extension.ts @@ -1,20 +1,18 @@ -import type { t } from '@traversable/schema' -import type { Equal, Unknown } from '@traversable/registry' -import type { ValidationError } from '@traversable/derive-validators' - import { toJsonSchema } from './toJsonSchema.js' import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' export let extension = { - toString(): 'string' { return 'string' }, - equals(left: string, right: string): boolean { return left === right }, - toJsonSchema(this: S): toJsonSchema { return toJsonSchema(this) }, + toString, + equals, + toJsonSchema, validate, } export interface Extension { - toString(): 'string' - equals: Equal - toJsonSchema(): toJsonSchema - validate(u: string | Unknown): true | ValidationError[] + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate } diff --git a/packages/schema-generator/test/test-data/string/toJsonSchema.ts b/packages/schema-generator/test/test-data/string/toJsonSchema.ts index f043be3c..4a2c1756 100644 --- a/packages/schema-generator/test/test-data/string/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/string/toJsonSchema.ts @@ -3,14 +3,20 @@ import type { t } from '@traversable/schema' import { has } from '@traversable/registry' import type { SizeBounds } from '@traversable/schema-to-json-schema' -export type toJsonSchema = Force<{ type: 'string' } & PickIfDefined> +export interface toJsonSchema { + (): Force<{ type: 'string' } & PickIfDefined> +} + +export function toJsonSchema(schema: t.string): toJsonSchema +export function toJsonSchema(schema: t.string): () => { type: 'string' } & Partial { + function stringToJsonSchema() { + const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null + const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null + let out: { type: 'string' } & Partial = { type: 'string' } + minLength !== null && void (out.minLength = minLength) + maxLength !== null && void (out.maxLength = maxLength) -export function toJsonSchema(schema: t.string): toJsonSchema -export function toJsonSchema(schema: t.string): { type: 'string' } & Partial { - const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null - const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null - let out: { type: 'string' } & Partial = { type: 'string' } - minLength !== null && void (out.minLength = minLength) - maxLength !== null && void (out.maxLength = maxLength) - return out + return out + } + return stringToJsonSchema } diff --git a/packages/schema-generator/test/test-data/string/toString.ts b/packages/schema-generator/test/test-data/string/toString.ts index f8d6f588..86a98e16 100644 --- a/packages/schema-generator/test/test-data/string/toString.ts +++ b/packages/schema-generator/test/test-data/string/toString.ts @@ -1 +1,2 @@ +export interface toString { (): 'string' } export function toString(): 'string' { return 'string' } diff --git a/packages/schema-generator/test/test-data/string/validate.ts b/packages/schema-generator/test/test-data/string/validate.ts index 13c60726..f73175b0 100644 --- a/packages/schema-generator/test/test-data/string/validate.ts +++ b/packages/schema-generator/test/test-data/string/validate.ts @@ -1,6 +1,14 @@ -import type { ValidationError } from '@traversable/derive-validators' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' import { NullaryErrors } from '@traversable/derive-validators' -export function validate(this: (u: any) => boolean, u: unknown, path: (keyof any)[] = []): true | ValidationError[] { - return this(u) || [NullaryErrors.string(u, path)] +export type validate = ValidationFn +export function validate(stringSchema: S): validate { + validateString.tag = URI.string + function validateString(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return stringSchema(u) || [NullaryErrors.number(u, path)] + } + return validateString } + diff --git a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts index 88e2190e..9f9c44e7 100644 --- a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts @@ -1,14 +1,25 @@ import type { Returns } from '@traversable/registry' -import type { t } from '@traversable/schema' +import { t } from '@traversable/schema' import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' import type { MinItems } from '@traversable/schema-to-json-schema' +// export interface toJsonSchema { +// (): [this] extends [infer S] ? { +// type: 'array', +// items: { [I in keyof S]: Returns } +// additionalItems: false +// minItems: MinItems +// maxItems: S['length' & keyof S] +// } +// : never +// } + export interface toJsonSchema { (): { type: 'array', items: { [I in keyof T]: Returns } additionalItems: false - minItems: MinItems + minItems: MinItems maxItems: T['length' & keyof T] } } @@ -19,13 +30,13 @@ export function toJsonSchema({ def }: t.tuple): type: 'array' items: unknown additionalItems: false - minItems?: number + minItems?: {} maxItems?: number } { - let min = minItems(def) - let max = def.length - let items = applyTupleOptionality(def, { min, max }) function tupleToJsonSchema() { + let min = minItems(def) + let max = def.length + let items = applyTupleOptionality(def, { min, max }) return { type: 'array' as const, additionalItems: false as const, diff --git a/packages/schema-generator/test/test-data/tuple/toString.ts b/packages/schema-generator/test/test-data/tuple/toString.ts index c4943b72..39954500 100644 --- a/packages/schema-generator/test/test-data/tuple/toString.ts +++ b/packages/schema-generator/test/test-data/tuple/toString.ts @@ -3,19 +3,24 @@ import { Array_isArray } from '@traversable/registry' import { t } from '@traversable/schema' import { hasToString } from '@traversable/schema-to-string' -export type toString = never | `[${Join<{ - [I in keyof T]: `${ - /* @ts-expect-error */ - T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType - }` -}, ', '>}]` +export interface toString { + (): never | `[${Join<{ + [I in keyof T]: `${ + /* @ts-expect-error */ + T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType + }` + }, ', '>}]` +} export function toString(tupleSchema: t.tuple): toString -export function toString(tupleSchema: t.tuple): string { - return Array_isArray(tupleSchema.def) - ? `[${tupleSchema.def.map( - (x) => t.optional.is(x) - ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` - : hasToString(x) ? x.toString() : 'unknown' - ).join(', ')}]` : 'unknown[]' +export function toString(tupleSchema: t.tuple): () => string { + function stringToString() { + return Array_isArray(tupleSchema.def) + ? `[${tupleSchema.def.map( + (x) => t.optional.is(x) + ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` + : hasToString(x) ? x.toString() : 'unknown' + ).join(', ')}]` : 'unknown[]' + } + return stringToString } diff --git a/packages/schema-generator/test/test-data/union/core.ts b/packages/schema-generator/test/test-data/union/core.ts index 75371200..a6363ae4 100644 --- a/packages/schema-generator/test/test-data/union/core.ts +++ b/packages/schema-generator/test/test-data/union/core.ts @@ -1,39 +1,42 @@ -import { bindUserDefinitions, Object_assign, URI } from '@traversable/registry' -import type { IntersectType } from '@traversable/schema' -import { t, Predicate } from '@traversable/schema' +import { + bindUserDefinitions, + Object_assign, + URI +} from '@traversable/registry' -export interface intersect extends intersect.core { - //<%= types %> -} +import { + t, + Predicate, +} from '@traversable/schema' -export function intersect(...schemas: S): intersect -export function intersect }>(...schemas: S): intersect -export function intersect(...schemas: S) { - return intersect.def(schemas) +export interface union extends union.core { + //<%= types %> } -export namespace intersect { - export let proto = {} as intersect - export function def(xs: readonly [...T]): intersect - export function def(xs: readonly [...T]): {} { +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: S): {} { return union.def(schemas) } +export namespace union { + export let proto = {} as union + export function def(xs: T): union + export function def(xs: T): {} { let userDefinitions = { //<%= terms %> } - const allOf = xs.every(t.isPredicate) ? Predicate.is.intersect(xs) : Predicate.is.unknown - function IntersectSchema(src: unknown) { return allOf(src) } - IntersectSchema.tag = URI.intersect - IntersectSchema.def = xs - Object_assign(IntersectSchema, intersect.proto) - return Object_assign(IntersectSchema, bindUserDefinitions(IntersectSchema, userDefinitions)) + const anyOf = xs.every(t.isPredicate) ? Predicate.is.union(xs) : Predicate.is.unknown + function UnionSchema(src: unknown) { return anyOf(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.proto) + return Object_assign(UnionSchema, bindUserDefinitions(UnionSchema, userDefinitions)) } } - -export declare namespace intersect { +export declare namespace union { interface core { (u: unknown): u is this['_type'] - tag: URI.intersect + tag: URI.union def: S - _type: IntersectType + _type: S[number & keyof S]['_type' & keyof S[number & keyof S]] } - type type> = never | T + type type = never | T } diff --git a/packages/schema-generator/test/test-data/union/toJsonSchema.ts b/packages/schema-generator/test/test-data/union/toJsonSchema.ts index 90fe2152..f025f027 100644 --- a/packages/schema-generator/test/test-data/union/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/union/toJsonSchema.ts @@ -2,8 +2,8 @@ import type { Returns } from '@traversable/registry' import { t } from '@traversable/schema' import { getSchema } from '@traversable/schema-to-json-schema' -export interface toJsonSchema { - (): { anyOf: { [I in keyof S]: Returns } } +export interface toJsonSchema { + (): { anyOf: { [I in keyof T]: Returns } } } export function toJsonSchema(unionSchema: t.union): toJsonSchema diff --git a/packages/schema-generator/test/test-data/union/toString.ts b/packages/schema-generator/test/test-data/union/toString.ts index 58a20dc5..91129980 100644 --- a/packages/schema-generator/test/test-data/union/toString.ts +++ b/packages/schema-generator/test/test-data/union/toString.ts @@ -3,11 +3,16 @@ import { Array_isArray } from '@traversable/registry' import { t } from '@traversable/schema' import { callToString } from '@traversable/schema-to-string' -export type toString = never | [T] extends [readonly []] ? 'never' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` +export interface toString { + (): never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` +} export function toString(unionSchema: t.union): toString -export function toString({ def }: t.union): string { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' +export function toString({ def }: t.union): () => string { + function unionToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' + } + return unionToString } diff --git a/packages/schema-to-json-schema/src/exports.ts b/packages/schema-to-json-schema/src/exports.ts index e7a62a4f..f0d66b56 100644 --- a/packages/schema-to-json-schema/src/exports.ts +++ b/packages/schema-to-json-schema/src/exports.ts @@ -2,7 +2,11 @@ import * as JsonSchema from './jsonSchema.js' type JsonSchema = import('./jsonSchema.js').JsonSchema export { JsonSchema } -export { applyTupleOptionality } from './jsonSchema.js' +export { + applyTupleOptionality, + getNumericBounds, +} from './jsonSchema.js' + export { toJsonSchema, fromJsonSchema } from './recursive.js' export { VERSION } from './version.js' export type { RequiredKeys } from './properties.js' From 0ae7adb191d98e060514d553b206b3fdcbb3fb1f Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 9 Apr 2025 22:55:15 -0500 Subject: [PATCH 18/45] feat(generator): adds integer schema --- packages/registry/src/types.ts | 3 + .../schema-generator/test/generated.test.ts | 6 -- .../schema-generator/test/imports.test.ts | 9 ++ .../test/test-data/integer/core.ts | 85 +++++++++++++++++++ .../test/test-data/integer/equals.ts | 6 ++ .../test/test-data/integer/extension.ts | 18 ++++ .../test/test-data/integer/toJsonSchema.ts | 23 +++++ .../test/test-data/integer/toString.ts | 2 + .../test/test-data/integer/validate.ts | 13 +++ .../test/test-data/number/extension.ts | 2 +- .../test/test-data/number/toJsonSchema.ts | 6 +- .../test/test-data/string/extension.ts | 4 +- .../test/test-data/string/toJsonSchema.ts | 6 +- packages/schema/src/schema.ts | 15 ++-- 14 files changed, 178 insertions(+), 20 deletions(-) create mode 100644 packages/schema-generator/test/test-data/integer/core.ts create mode 100644 packages/schema-generator/test/test-data/integer/equals.ts create mode 100644 packages/schema-generator/test/test-data/integer/extension.ts create mode 100644 packages/schema-generator/test/test-data/integer/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/integer/toString.ts create mode 100644 packages/schema-generator/test/test-data/integer/validate.ts diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index 53d645ec..c7e50cc9 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -21,6 +21,9 @@ export interface Record extends newtype<{ [P in K]+?: V export interface Array extends newtype { } export interface ReadonlyArray extends newtype { } +export type Integer> = [T] extends [number] + ? [Z] extends [`${number}.${string}`] ? never + : number : never // transforms export type Force = never | { -readonly [K in keyof T]: T[K] } diff --git a/packages/schema-generator/test/generated.test.ts b/packages/schema-generator/test/generated.test.ts index 94385d59..b02a647e 100644 --- a/packages/schema-generator/test/generated.test.ts +++ b/packages/schema-generator/test/generated.test.ts @@ -10,9 +10,3 @@ let ys = tuple(string).toJsonSchema() let zs = object({ a: number }).toJsonSchema() let as = object({ a: string }).toJsonSchema() let bs = union(string).toJsonSchema() - -let cs = intersect(string, number).toString() -let ds = tuple(string).toString() -let es = object({ a: number }).toString() -let gs = union(string, number).toString() -let hs = object({ a: object({ b: tuple(number, intersect(object({ c: string }), object({ d: union(number, string) }))) }) }).toString() diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index 84960a86..037d9388 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -34,6 +34,14 @@ let PATH = { toString: path.join(DATA_PATH, 'string', 'toString.ts'), validate: path.join(DATA_PATH, 'string', 'validate.ts'), }, + integer: { + core: path.join(DATA_PATH, 'integer', 'core.ts'), + extension: path.join(DATA_PATH, 'integer', 'extension.ts'), + equals: path.join(DATA_PATH, 'integer', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'integer', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'integer', 'toString.ts'), + validate: path.join(DATA_PATH, 'integer', 'validate.ts'), + }, number: { core: path.join(DATA_PATH, 'number', 'core.ts'), extension: path.join(DATA_PATH, 'number', 'extension.ts'), @@ -79,6 +87,7 @@ let PATH = { array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), intersect: path.join(DIR_PATH, '__generated__', 'intersect.gen.ts'), number: path.join(DIR_PATH, '__generated__', 'number.gen.ts'), + integer: path.join(DIR_PATH, '__generated__', 'integer.gen.ts'), object: path.join(DIR_PATH, '__generated__', 'object.gen.ts'), string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), tuple: path.join(DIR_PATH, '__generated__', 'tuple.gen.ts'), diff --git a/packages/schema-generator/test/test-data/integer/core.ts b/packages/schema-generator/test/test-data/integer/core.ts new file mode 100644 index 00000000..2b798c86 --- /dev/null +++ b/packages/schema-generator/test/test-data/integer/core.ts @@ -0,0 +1,85 @@ +import type { Integer } from '@traversable/registry' +import { + Math_min, + Math_max, + Number_isSafeInteger, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Bounds } from '@traversable/schema' +import { __carryover as carryover, __within as within } from '@traversable/schema' + +export let userDefinitions = { + //<%= terms %> +} + +export { integer } +interface integer extends integer.core { + //<%= types %> +} + +declare namespace integer { + interface core extends integer.methods { + (u: unknown): u is this['_type'] + _type: number + tag: URI.integer + def: this['_type'] + minimum?: number + maximum?: number + } + interface methods { + min>(minimum: Min): integer.Min + max>(maximum: Max): integer.Max + between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maximum: number }] + ? integer.between<[min: X, max: Self['maximum']]> + : integer.min + type Max + = [Self] extends [{ minimum: number }] + ? integer.between<[min: Self['minimum'], max: X]> + : integer.max + interface min extends integer { minimum: Min } + interface max extends integer { maximum: Max } + interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } +} +const integer = Object_assign( + function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) }, + userDefinitions, +) as integer + +integer.tag = URI.integer +integer.def = 0 +integer.min = function integerMin(minimum) { + return Object_assign( + boundedInteger({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +integer.max = function integerMax(maximum) { + return Object_assign( + boundedInteger({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +integer.between = function integerBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedInteger({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedIntegerSchema(u: unknown) { + return integer(u) && within(bounds)(u) + }, carry, integer) +} diff --git a/packages/schema-generator/test/test-data/integer/equals.ts b/packages/schema-generator/test/test-data/integer/equals.ts new file mode 100644 index 00000000..15704197 --- /dev/null +++ b/packages/schema-generator/test/test-data/integer/equals.ts @@ -0,0 +1,6 @@ +import { Equal } from "@traversable/registry" + +export type equals = Equal +export function equals(left: number, right: number): boolean { + return Equal.SameValueNumber(left, right) +} diff --git a/packages/schema-generator/test/test-data/integer/extension.ts b/packages/schema-generator/test/test-data/integer/extension.ts new file mode 100644 index 00000000..0b36c0f8 --- /dev/null +++ b/packages/schema-generator/test/test-data/integer/extension.ts @@ -0,0 +1,18 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Extension { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let extension = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/test/test-data/integer/toJsonSchema.ts b/packages/schema-generator/test/test-data/integer/toJsonSchema.ts new file mode 100644 index 00000000..cc15fa02 --- /dev/null +++ b/packages/schema-generator/test/test-data/integer/toJsonSchema.ts @@ -0,0 +1,23 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '@traversable/schema' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.integer): toJsonSchema { + function integerToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'integer' as const, + ...bounds, + } + } + return integerToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/integer/toString.ts b/packages/schema-generator/test/test-data/integer/toString.ts new file mode 100644 index 00000000..912565e6 --- /dev/null +++ b/packages/schema-generator/test/test-data/integer/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } diff --git a/packages/schema-generator/test/test-data/integer/validate.ts b/packages/schema-generator/test/test-data/integer/validate.ts new file mode 100644 index 00000000..21383d41 --- /dev/null +++ b/packages/schema-generator/test/test-data/integer/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(integerSchema: S): validate { + validateInteger.tag = URI.integer + function validateInteger(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return integerSchema(u) || [NullaryErrors.integer(u, path)] + } + return validateInteger +} diff --git a/packages/schema-generator/test/test-data/number/extension.ts b/packages/schema-generator/test/test-data/number/extension.ts index a3c0f99f..0b36c0f8 100644 --- a/packages/schema-generator/test/test-data/number/extension.ts +++ b/packages/schema-generator/test/test-data/number/extension.ts @@ -6,7 +6,7 @@ import { equals } from './equals.js' export interface Extension { toString: toString equals: equals - toJsonSchema: toJsonSchema + toJsonSchema: toJsonSchema validate: validate } diff --git a/packages/schema-generator/test/test-data/number/toJsonSchema.ts b/packages/schema-generator/test/test-data/number/toJsonSchema.ts index 371ea7c1..f64b1b0f 100644 --- a/packages/schema-generator/test/test-data/number/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/number/toJsonSchema.ts @@ -3,10 +3,10 @@ import type { t } from '@traversable/schema' import type { NumericBounds } from '@traversable/schema-to-json-schema' import { getNumericBounds } from '@traversable/schema-to-json-schema' -export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } +export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } -export function toJsonSchema(schema: t.number): toJsonSchema -export function toJsonSchema(schema: t.number): toJsonSchema { +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.number): toJsonSchema { function numberToJsonSchema() { const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) let bounds: NumericBounds = {} diff --git a/packages/schema-generator/test/test-data/string/extension.ts b/packages/schema-generator/test/test-data/string/extension.ts index 3a592518..8030c201 100644 --- a/packages/schema-generator/test/test-data/string/extension.ts +++ b/packages/schema-generator/test/test-data/string/extension.ts @@ -11,8 +11,8 @@ export let extension = { } export interface Extension { - toString: toString equals: equals - toJsonSchema: toJsonSchema + toJsonSchema: toJsonSchema + toString: toString validate: validate } diff --git a/packages/schema-generator/test/test-data/string/toJsonSchema.ts b/packages/schema-generator/test/test-data/string/toJsonSchema.ts index 4a2c1756..d39b23e1 100644 --- a/packages/schema-generator/test/test-data/string/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/string/toJsonSchema.ts @@ -3,11 +3,11 @@ import type { t } from '@traversable/schema' import { has } from '@traversable/registry' import type { SizeBounds } from '@traversable/schema-to-json-schema' -export interface toJsonSchema { - (): Force<{ type: 'string' } & PickIfDefined> +export interface toJsonSchema { + (): Force<{ type: 'string' } & PickIfDefined> } -export function toJsonSchema(schema: t.string): toJsonSchema +export function toJsonSchema(schema: S): toJsonSchema export function toJsonSchema(schema: t.string): () => { type: 'string' } & Partial { function stringToJsonSchema() { const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index 7437d9f2..f4046cd7 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -9,7 +9,16 @@ import type { TypeError, } from '@traversable/registry' -import { applyOptions, fn, getConfig, has, omitMethods, parseArgs, symbol, URI } from '@traversable/registry' +import { + applyOptions, + fn, + getConfig, + has, + Number_isSafeInteger, + parseArgs, + symbol, + URI, +} from '@traversable/registry' import type { Guard, @@ -32,10 +41,6 @@ const Object_keys = globalThis.Object.keys /** @internal */ const Array_isArray = globalThis.Array.isArray -/** @internal */ -const Number_isSafeInteger - : (u: unknown) => u is number - = globalThis.Number.isSafeInteger as never /** @internal */ const Math_min = globalThis.Math.min From ad344c868406c6cc7f7bdc7e5eb29d2987328b28 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 10 Apr 2025 13:24:24 -0500 Subject: [PATCH 19/45] feat(generator): adds the following schemas - `bigint` - `eq` - `optional` - `record` --- package.json | 5 +- packages/derive-equals/src/install.ts | 4 +- packages/derive-validators/src/exports.ts | 6 +- packages/derive-validators/src/shared.ts | 20 +- ...erDefinitions.ts => bindUserExtensions.ts} | 3 +- packages/registry/src/exports.ts | 4 +- packages/registry/src/globalThis.ts | 12 +- .../registry/src/replaceBooleanConstructor.ts | 7 - packages/registry/src/safeCoerce.ts | 10 + .../src/__generated__/__manifest__.ts | 3 +- .../schema-generator/src/defineExtension.ts | 7 + packages/schema-generator/src/exports.ts | 9 +- packages/schema-generator/src/generate.ts | 95 ++++ packages/schema-generator/src/imports.ts | 105 +---- .../src/parser-combinators.ts | 1 - packages/schema-generator/src/parser.ts | 416 ++++++++++-------- packages/schema-generator/test/e2e.test.ts | 333 ++++++++++++++ .../schema-generator/test/generated.test.ts | 12 - .../schema-generator/test/imports.test.ts | 179 +++++--- packages/schema-generator/test/namespace.ts | 10 + .../test/test-data/array/core.ts | 44 +- .../test/test-data/array/equals.ts | 12 +- .../test/test-data/array/extension.ts | 22 +- .../test/test-data/array/toJsonSchema.ts | 12 +- .../test/test-data/array/toString.ts | 18 +- .../test/test-data/array/validate.ts | 29 +- .../test/test-data/bigint/core.ts | 98 +++++ .../test/test-data/bigint/equals.ts | 7 + .../test/test-data/bigint/extension.ts | 21 + .../test/test-data/bigint/toJsonSchema.ts | 9 + .../test/test-data/bigint/toString.ts | 2 + .../test/test-data/bigint/validate.ts | 13 + .../test/test-data/eq/core.ts | 40 ++ .../test/test-data/eq/equals.ts | 8 + .../test/test-data/eq/extension.ts | 21 + .../test/test-data/eq/toJsonSchema.ts | 8 + .../test/test-data/eq/toString.ts | 17 + .../test/test-data/eq/validate.ts | 17 + .../test/test-data/integer/core.ts | 79 ++-- .../test/test-data/integer/extension.ts | 11 +- .../test/test-data/integer/validate.ts | 2 +- .../test/test-data/intersect/core.ts | 26 +- .../test/test-data/intersect/extension.ts | 14 +- .../test/test-data/intersect/toString.ts | 2 +- .../test/test-data/intersect/validate.ts | 2 +- .../test/test-data/number/core.ts | 32 +- .../test/test-data/number/extension.ts | 7 +- .../test/test-data/object/core.ts | 32 +- .../test/test-data/object/extension.ts | 18 +- .../test/test-data/object/toString.ts | 21 +- .../test/test-data/object/validate.ts | 33 +- .../test/test-data/optional/core.ts | 43 ++ .../test/test-data/optional/equals.ts | 13 + .../test/test-data/optional/extension.ts | 21 + .../test/test-data/optional/toJsonSchema.ts | 19 + .../test/test-data/optional/toString.ts | 15 + .../test/test-data/optional/validate.ts | 17 + .../test/test-data/record/core.ts | 41 ++ .../test/test-data/record/equals.ts | 32 ++ .../test/test-data/record/extension.ts | 20 + .../test/test-data/record/toJsonSchema.ts | 21 + .../test/test-data/record/toString.ts | 17 + .../test/test-data/record/validate.ts | 24 + .../test/test-data/string/core.ts | 38 +- .../test/test-data/string/extension.ts | 19 +- .../test/test-data/string/validate.ts | 2 +- .../test/test-data/tuple/core.ts | 37 +- .../test/test-data/tuple/extension.ts | 8 +- .../test/test-data/tuple/toJsonSchema.ts | 12 - .../test/test-data/tuple/validate.ts | 2 +- .../test/test-data/union/core.ts | 42 +- .../test/test-data/union/extension.ts | 8 +- .../test/test-data/union/validate.ts | 2 +- packages/schema-to-json-schema/src/exports.ts | 2 +- .../schema-to-json-schema/src/jsonSchema.ts | 2 +- packages/schema-to-string/src/exports.ts | 1 + packages/schema-to-string/src/shared.ts | 8 +- packages/schema-to-string/src/toString.ts | 9 +- .../schema-to-string/test/toString.test.ts | 2 + packages/schema/src/exports.ts | 2 - packages/schema/src/schema.ts | 13 +- packages/schema/test/schema.test.ts | 9 +- 82 files changed, 1727 insertions(+), 692 deletions(-) rename packages/registry/src/{bindUserDefinitions.ts => bindUserExtensions.ts} (50%) delete mode 100644 packages/registry/src/replaceBooleanConstructor.ts create mode 100644 packages/registry/src/safeCoerce.ts create mode 100644 packages/schema-generator/src/defineExtension.ts create mode 100644 packages/schema-generator/src/generate.ts create mode 100644 packages/schema-generator/test/e2e.test.ts delete mode 100644 packages/schema-generator/test/generated.test.ts create mode 100644 packages/schema-generator/test/namespace.ts create mode 100644 packages/schema-generator/test/test-data/bigint/core.ts create mode 100644 packages/schema-generator/test/test-data/bigint/equals.ts create mode 100644 packages/schema-generator/test/test-data/bigint/extension.ts create mode 100644 packages/schema-generator/test/test-data/bigint/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/bigint/toString.ts create mode 100644 packages/schema-generator/test/test-data/bigint/validate.ts create mode 100644 packages/schema-generator/test/test-data/eq/core.ts create mode 100644 packages/schema-generator/test/test-data/eq/equals.ts create mode 100644 packages/schema-generator/test/test-data/eq/extension.ts create mode 100644 packages/schema-generator/test/test-data/eq/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/eq/toString.ts create mode 100644 packages/schema-generator/test/test-data/eq/validate.ts create mode 100644 packages/schema-generator/test/test-data/optional/core.ts create mode 100644 packages/schema-generator/test/test-data/optional/equals.ts create mode 100644 packages/schema-generator/test/test-data/optional/extension.ts create mode 100644 packages/schema-generator/test/test-data/optional/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/optional/toString.ts create mode 100644 packages/schema-generator/test/test-data/optional/validate.ts create mode 100644 packages/schema-generator/test/test-data/record/core.ts create mode 100644 packages/schema-generator/test/test-data/record/equals.ts create mode 100644 packages/schema-generator/test/test-data/record/extension.ts create mode 100644 packages/schema-generator/test/test-data/record/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/record/toString.ts create mode 100644 packages/schema-generator/test/test-data/record/validate.ts diff --git a/package.json b/package.json index fe4b7943..b165406a 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,10 @@ "bench": "pnpm vitest bench --outputJson benchmarks/benchmark--$(date -Iseconds).json", "boot": "pnpm install && pnpm reboot", "build": "pnpm build:root && pnpm run build:pre && pnpm --recursive --parallel run build && pnpm build:post", + "build:docs": "pnpm dlx tsx ./bin/docs.ts", "build:pre": "pnpm dlx tsx ./bin/bump.ts", "build:pkgs": "pnpm --filter \"@traversable/*\" run \"/^build:.*/\"", - "build:root": "tsc -b tsconfig.build.json && pnpm run docs", + "build:root": "tsc -b tsconfig.build.json && pnpm run build:docs", "build:post": "pnpm circular", "build:dist": "pnpm dlx tsx ./bin/pack.ts", "changes": "changeset add", @@ -24,10 +25,10 @@ "describe": "pnpm run \"/^describe:.*/\"", "describe:project": "./bin/describe.ts", "dev": "pnpm --filter \"@traversable/sandbox\" run \"dev\"", - "docs": "pnpm dlx tsx ./bin/docs.ts", "reboot": "./bin/reboot.ts", "test": "vitest run -- --skipTypes && tsc -b tsconfig.json", "test:cov": "pnpm vitest run --coverage", + "test:e2e": "pnpm vitest run --coverage", "test:watch": "pnpm vitest --coverage", "test:watch:no-cov": "pnpm vitest", "workspace:new": "./bin/workspace-create.ts", diff --git a/packages/derive-equals/src/install.ts b/packages/derive-equals/src/install.ts index 078c8fa1..2ddb2716 100644 --- a/packages/derive-equals/src/install.ts +++ b/packages/derive-equals/src/install.ts @@ -1,4 +1,4 @@ -import { Equal, has } from '@traversable/registry' +import { Equal } from '@traversable/registry' import { t } from '@traversable/schema' import * as Eq from './equals.js' @@ -98,7 +98,7 @@ export function recordEquals( if (!hasOwn(l, k)) return false if (!(this.def.equals(l[k], r[k]))) return false } - return Eq.record(this.def.equals)(l, r) + return true } export function unionEquals(this: t.union<{ equals: Equal }[]>, diff --git a/packages/derive-validators/src/exports.ts b/packages/derive-validators/src/exports.ts index 4a7db2da..b0aac5ad 100644 --- a/packages/derive-validators/src/exports.ts +++ b/packages/derive-validators/src/exports.ts @@ -7,7 +7,11 @@ export type { Validate, Options, } from './shared.js' -export { hasOptionalSymbol } from './shared.js' +export { + hasOptionalSymbol, + hasValidate, + callValidate, +} from './shared.js' export type { ValidationError, diff --git a/packages/derive-validators/src/shared.ts b/packages/derive-validators/src/shared.ts index e01d86b3..852f8407 100644 --- a/packages/derive-validators/src/shared.ts +++ b/packages/derive-validators/src/shared.ts @@ -1,5 +1,5 @@ import type { Unknown } from '@traversable/registry' -import { symbol } from '@traversable/registry' +import { has, symbol } from '@traversable/registry' import type { t, SchemaOptions } from '@traversable/schema' import type { ValidationError } from './errors.js' @@ -8,13 +8,17 @@ export interface Options extends SchemaOptions { path: (keyof any)[] } -export type Validate = never | { (u: T | {} | null | undefined): true | ValidationError[] } - -export type ValidationFn = never | { - (u: T | Unknown, path?: (keyof any)[]): true | ValidationError[]; -} - +export type Validate = never | { (u: T | Unknown): true | ValidationError[] } +export type ValidationFn = never | { (u: T | Unknown, path?: (keyof any)[]): true | ValidationError[] } export interface Validator { validate: ValidationFn } -export const hasOptionalSymbol = (u: unknown): u is t.optional => +export let hasOptionalSymbol = (u: unknown): u is t.optional => !!u && typeof u === 'function' && symbol.optional in u && typeof u[symbol.optional] === 'number' + +export let hasValidate + : (u: unknown) => u is Validator + = has('validate', (u) => typeof u === 'function') as never + +export let callValidate + : (schema: { validate?: unknown }, u: unknown, path?: (keyof any)[]) => true | ValidationError[] + = (schema, u, path = Array.of()) => hasValidate(schema) ? schema.validate(u, path) : true diff --git a/packages/registry/src/bindUserDefinitions.ts b/packages/registry/src/bindUserExtensions.ts similarity index 50% rename from packages/registry/src/bindUserDefinitions.ts rename to packages/registry/src/bindUserExtensions.ts index 73a8c4e1..cd1e5d2a 100644 --- a/packages/registry/src/bindUserDefinitions.ts +++ b/packages/registry/src/bindUserExtensions.ts @@ -1,4 +1,5 @@ -export let bindUserDefinitions = (schema: {}, userDefinitions: Record) => { +export function bindUserExtensions(schema: T, userDefinitions: Record): T & { _type: any } +export function bindUserExtensions(schema: T, userDefinitions: Record) { for (let k in userDefinitions) { userDefinitions[k] = typeof userDefinitions[k] === 'function' diff --git a/packages/registry/src/exports.ts b/packages/registry/src/exports.ts index acd6b2ad..ce867a34 100644 --- a/packages/registry/src/exports.ts +++ b/packages/registry/src/exports.ts @@ -43,8 +43,8 @@ export { } from './has.js' export { unsafeCompact } from './compact.js' -export { bindUserDefinitions } from './bindUserDefinitions.js' -export { replaceBooleanConstructor } from './replaceBooleanConstructor.js' +export { bindUserExtensions } from './bindUserExtensions.js' +export { safeCoerce } from './safeCoerce.js' export { map } from './mapObject.js' diff --git a/packages/registry/src/globalThis.ts b/packages/registry/src/globalThis.ts index a2a49ddb..6a9516f3 100644 --- a/packages/registry/src/globalThis.ts +++ b/packages/registry/src/globalThis.ts @@ -13,14 +13,19 @@ import type { } from "@traversable/registry/satisfies" export const Array_isArray - : (u: unknown) => u is readonly T[] + : (u: unknown) => u is T[] = globalThis.Array.isArray export const Math_max = globalThis.Math.min export const Math_min = globalThis.Math.min -export const Number_isSafeInteger = globalThis.Number.isSafeInteger -export const Number_isInteger = globalThis.Number.isInteger +export const Number_isInteger + : (x: unknown) => x is number + = globalThis.Number.isInteger as never + +export const Number_isSafeInteger + : (x: unknown) => x is number + = globalThis.Number.isSafeInteger as never export const Object_assign = globalThis.Object.assign export const Object_is = globalThis.Object.is @@ -34,7 +39,6 @@ export const Object_keys : (x: T) => (K)[] = globalThis.Object.keys - export type Object_entries = never | (K extends K ? [k: K, v: T[K & keyof T]] : never)[] export const Object_entries: { >(x: T): MixedNonFiniteEntries diff --git a/packages/registry/src/replaceBooleanConstructor.ts b/packages/registry/src/replaceBooleanConstructor.ts deleted file mode 100644 index 9fbec604..00000000 --- a/packages/registry/src/replaceBooleanConstructor.ts +++ /dev/null @@ -1,7 +0,0 @@ -/** @internal */ -const isBooleanConstructor = (u: unknown): u is globalThis.BooleanConstructor => u === globalThis.Boolean - -export function replaceBooleanConstructor(replacement: (u: unknown) => u is {}): - (fn: typeof globalThis.Boolean | T) => T | ((u: unknown) => u is {}) { - return (fn) => isBooleanConstructor(fn) ? replacement : fn -} diff --git a/packages/registry/src/safeCoerce.ts b/packages/registry/src/safeCoerce.ts new file mode 100644 index 00000000..c6520894 --- /dev/null +++ b/packages/registry/src/safeCoerce.ts @@ -0,0 +1,10 @@ +/** @internal */ +let isBooleanConstructor = (u: unknown): u is globalThis.BooleanConstructor => u === globalThis.Boolean + +/** @internal */ +let isNonNullable = (u: unknown) => u != null + +export function safeCoerce(fn: T): T +export function safeCoerce(fn: T): T { + return isBooleanConstructor(fn) ? isNonNullable as never : fn +} diff --git a/packages/schema-generator/src/__generated__/__manifest__.ts b/packages/schema-generator/src/__generated__/__manifest__.ts index 16187ea9..f26b6529 100644 --- a/packages/schema-generator/src/__generated__/__manifest__.ts +++ b/packages/schema-generator/src/__generated__/__manifest__.ts @@ -43,6 +43,7 @@ export default { "@traversable/derive-validators": "workspace:^", "@traversable/schema-to-json-schema": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^" } } as const \ No newline at end of file diff --git a/packages/schema-generator/src/defineExtension.ts b/packages/schema-generator/src/defineExtension.ts new file mode 100644 index 00000000..0004e1bc --- /dev/null +++ b/packages/schema-generator/src/defineExtension.ts @@ -0,0 +1,7 @@ +import type { ParsedExtensionFile } from './parser.js' + +export type DefineExtension = Required + +export function defineExtension(extension: DefineExtension): DefineExtension { + return extension +} diff --git a/packages/schema-generator/src/exports.ts b/packages/schema-generator/src/exports.ts index 4fdcc190..cc5ef447 100644 --- a/packages/schema-generator/src/exports.ts +++ b/packages/schema-generator/src/exports.ts @@ -1,6 +1,9 @@ export * from './version.js' export * as P from './parser-combinators.js' +export type { DefineExtension } from './defineExtension.js' +export { defineExtension } from './defineExtension.js' + export type { ExtensionsBySchemaName, ParsedImport, @@ -12,9 +15,13 @@ export { makeImport, makeImports, makeImportsBySchemaName, - writeSchemas, } from './imports.js' +export { + generateSchemas, + writeSchemas, +} from './generate.js' + export type { ParsedSourceFile, } from './parser.js' diff --git a/packages/schema-generator/src/generate.ts b/packages/schema-generator/src/generate.ts new file mode 100644 index 00000000..12e82639 --- /dev/null +++ b/packages/schema-generator/src/generate.ts @@ -0,0 +1,95 @@ +import * as fs from 'node:fs' +import { + fn, + omit_ as omit, + pick_ as pick, +} from "@traversable/registry" + +import type { ParsedSourceFile, ParsedExtensionFile } from './parser.js' +import { + parseExtensionFile, + parseFile, + replaceExtensions, +} from './parser.js' +import { makeImports } from './imports.js' + +let isKeyOf = (k: keyof any, t: T): k is keyof T => !!t && typeof t === 'object' && k in t + +let makeSchemaFileHeader = (schemaName: string) => [ + ` +/** + * t.${schemaName} schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +`.trim(), +].join('\n') + +let makeHeaderComment = (header: string) => [ + `///////` + '/'.repeat(header.length) + `///////`, + `/// ` + header + ` ///`, +].join('\n') + +let makeFooterComment = (footer: string) => [ + `/// ` + footer + ` ///`, + `///////` + '/'.repeat(footer.length) + `///////`, +].join('\n') + +function makeSchemaFileContent( + schemaName: string, + parsedSourceFiles: Record, + parsedExtensionFile: ParsedExtensionFile, + imports: string, +) { + let core = replaceExtensions(pick(parsedSourceFiles, 'core').core.body, parsedExtensionFile) + let noCore = omit(parsedSourceFiles, 'core') + let files = fn.pipe( + fn.map(noCore, (source) => source.body.trim()), + Object.entries, + fn.map(([k, body]) => [ + makeHeaderComment(k), + body, + makeFooterComment(k), + ].join('\n')), + ) + + return [ + makeSchemaFileHeader(schemaName), + imports, + ...files.map((ext) => '\r' + ext), + '\r', + core, + ] +} + +export function generateSchemas>>( + sources: T, + targets: Record +): [path: string, content: string][] + +export function generateSchemas( + sources: Record>, + targets: Record +): [path: string, content: string][] { + let parsedSourceFiles = fn.map(sources, fn.map(parseFile)) + let exts = fn.map(sources, (src) => pick(src, 'extension').extension) + let noExts = fn.map(parsedSourceFiles, (src) => omit(src, 'extension')) + let parsedExtensionFiles = fn.map(exts, parseExtensionFile) + let importsBySchemaName = makeImports(fn.map(parsedSourceFiles, fn.map((_) => _.imports))) + let contentBySchemaName = fn.map( + noExts, + (v, k) => makeSchemaFileContent(k, v, parsedExtensionFiles[k], importsBySchemaName[k]) + ) + + return Object.entries(contentBySchemaName).map(([k, content]) => { + if (!isKeyOf(k, targets)) throw Error('No write target found for schema type "' + k + '"') + else return [targets[k], content.join('\n') + '\n'] satisfies [any, any] + }) +} + +export function writeSchemas>>(sources: T, targets: Record): void +export function writeSchemas(...args: [sources: Record>, targets: Record]): void { + let schemas = generateSchemas(...args) + for (let [target, content] of schemas) { + void fs.writeFileSync(target, content) + } +} diff --git a/packages/schema-generator/src/imports.ts b/packages/schema-generator/src/imports.ts index d8ea3437..5d3efa2c 100644 --- a/packages/schema-generator/src/imports.ts +++ b/packages/schema-generator/src/imports.ts @@ -1,27 +1,8 @@ -import * as fs from 'node:fs' import type * as T from '@traversable/registry' - +import { fn, Array_isArray } from "@traversable/registry" import { t } from '@traversable/schema' -import type { - Comparator, - Etc as ForExample, -} from '@traversable/registry' -import { - fn, - Array_isArray, - omit_ as omit, - pick_ as pick, -} from "@traversable/registry" - -import type { ParsedSourceFile } from './parser.js' -import { - parseExtensionFile, - parseFile, - replaceExtensions, -} from './parser.js' - -let stringComparator: Comparator = (l, r) => l.localeCompare(r) +let stringComparator: T.Comparator = (l, r) => l.localeCompare(r) export type Import = t.typeof export let Import = t.object({ @@ -46,7 +27,7 @@ export type ExtensionsBySchemaName = T.Record< > > -export const ExtensionsBySchemaName = t.record(SchemaDependencies) +export let ExtensionsBySchemaName = t.record(SchemaDependencies) export type DeduplicatedImport = { named: Set @@ -196,86 +177,6 @@ export function makeImports(extensionsBySchemaName: ExtensionsBySchemaName): {} ) } -let isKeyOf = (k: keyof any, t: T): k is keyof T => !!t && typeof t === 'object' && k in t - -let makeSchemaFileHeader = (schemaName: string) => [ - ` -/** - * t.${schemaName} schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -`.trim(), -].join('\n') - -let makeHeaderComment = (header: string) => [ - `///////` + '/'.repeat(header.length) + `///////`, - `/// ` + header + ` ///`, -].join('\n') - -let makeFooterComment = (footer: string) => [ - `/// ` + footer + ` ///`, - `///////` + '/'.repeat(footer.length) + `///////`, -].join('\n') - -function makeSchemaFileContent( - schemaName: string, - parsedSourceFiles: Record, - parsedExtensionFile: { term?: string, type?: string }, - imports: string, -) { - let withoutCore = omit(parsedSourceFiles, 'core') - let core = replaceExtensions(pick(parsedSourceFiles, 'core').core.body, parsedExtensionFile) - let files = fn.pipe( - fn.map(withoutCore, (source) => source.body.trim()), - Object.entries, - fn.map(([k, body]) => [ - makeHeaderComment(k), - body, - makeFooterComment(k), - ].join('\n')), - ) - - return [ - makeSchemaFileHeader(schemaName), - imports, - ...files.map((ext) => '\r' + ext), - '\r', - core, - ] -} - -export function generateSchemas>>( - sources: T, - targets: Record -): [path: string, content: string][] -export function generateSchemas( - sources: Record>, - targets: Record -): [path: string, content: string][] { - let parsedSourceFiles = fn.map(sources, fn.map(parseFile)) - let noExts = fn.map(parsedSourceFiles, (src) => omit(src, 'extension')) - let exts = fn.map(sources, (src) => pick(src, 'extension').extension) - let parsedExtensionFiles = fn.map(exts, parseExtensionFile) - let importsBySchemaName = makeImports(fn.map(parsedSourceFiles, fn.map((_) => _.imports))) - let contentBySchemaName = fn.map( - noExts, - (v, k) => makeSchemaFileContent(k, v, parsedExtensionFiles[k], importsBySchemaName[k]) - ) - - return Object.entries(contentBySchemaName).map(([k, content]) => { - if (!isKeyOf(k, targets)) throw Error('NO target found for schema type ' + k) - else return [targets[k], content.join('\n') + '\n'] satisfies [any, any] - }) -} - -export function writeSchemas>>(sources: T, targets: Record): void -export function writeSchemas(...args: [sources: Record>, targets: Record]): void { - let schemas = generateSchemas(...args) - for (let [target, content] of schemas) { - void fs.writeFileSync(target, content) - } -} - // let getDependenciesFromImportsBySchemaName = (extensionsBySchemaName: ExtensionsBySchemaName) => { // let xs = Object.values(extensionsBySchemaName) // .filter((_) => !!_) diff --git a/packages/schema-generator/src/parser-combinators.ts b/packages/schema-generator/src/parser-combinators.ts index 2b5d00f5..10b315e1 100644 --- a/packages/schema-generator/src/parser-combinators.ts +++ b/packages/schema-generator/src/parser-combinators.ts @@ -350,7 +350,6 @@ export function language(source: { [x: string]: (go: Record { const parser = loop(lang) - console.log('parser', parser) if (parser == null || !(parser instanceof Parser)) { throw new Error('syntax must return a Parser.') } diff --git a/packages/schema-generator/src/parser.ts b/packages/schema-generator/src/parser.ts index 528e4780..04381b92 100644 --- a/packages/schema-generator/src/parser.ts +++ b/packages/schema-generator/src/parser.ts @@ -1,67 +1,65 @@ import * as fs from 'node:fs' import ts from 'typescript' + import type { Imports } from './imports.js' import * as P from './parser-combinators.js' -import { fn } from '@traversable/registry' +import { Array_isArray, Comparator } from '@traversable/registry' export type ParsedSourceFile = { imports: Record body: string - // extension?: { type?: string, term?: string } } -let typesMarker = '//<%= types %>' as const -let termsMarker = '//<%= terms %>' as const - let LIB_FILE_NAME = '/lib/lib.d.ts' -let LIB = [ - 'interface Boolean {}', - 'interface Function {}', - 'interface CallableFunction {}', - 'interface NewableFunction {}', - 'interface IArguments {}', - 'interface Number { toExponential: any }', - 'interface Object {}', - 'interface RegExp {}', - 'interface String { charAt: any }', - 'interface Array { length: number [n: number]: T }', - 'interface ReadonlyArray {}', - 'declare const console: { log(msg: any): void }', - '/// ', -].join('\n') - -let key = P.seq( - P.optional(P.char('"')), - P.ident, - P.optional(P.char('"')), -).map(([, k]) => k) - -let propertyValue = P.char().many({ not: P.alt(P.char(','), P.char('}')) }).map((xs) => xs.join('')) - -let entry = P.seq( - key, - P.optional(P.whitespace), - P.char(':'), - P.optional(P.whitespace), - propertyValue, -).map(([key, , , , value]) => [key, value] as [k: string, v: string]) - -let comma = P.seq( - P.spaces, - P.char(','), - P.spaces, -).map((_) => _[1]) - -let entriesWithOptionalDanglingComma = P.seq( - P.seq(entry.trim(), P.char(',')).map(([_]) => _).many(), - P.optional(entry.trim()), -).map(([xs, x]) => x === null ? xs : [...xs, x]) - -let parseObjectEntries = P.index([ - P.char('{'), - P.trim(entriesWithOptionalDanglingComma), - P.char('}'), -], 1).map((_) => _ === null ? [] : _) +let LIB = [].join('\n') + +export let VarName = { + Type: 'Types', + Def: 'Definitions', + Ext: 'Extensions', +} as const + +export type ParsedExtensionFile = never | { + [VarName.Type]?: string + [VarName.Def]?: string + [VarName.Ext]?: string +} + + +export let typesMarker = `//<%= ${VarName.Type} %>` as const +export let definitionsMarker = `//<%= ${VarName.Def} %>` as const +export let extensionsMarker = `//<%= ${VarName.Ext} %>` as const + +export function createProgram(source: string): ts.Program { + let filename = '/source.ts' + let files = new Map() + files.set(filename, source) + files.set(LIB_FILE_NAME, LIB) + return ts.createProgram( + [filename], { + target: ts.ScriptTarget.ESNext, + module: ts.ModuleKind.ESNext, + strict: true, + noEmit: true, + isolatedModules: true, + types: [], + }, { + fileExists: (filename) => files.has(filename), + getCanonicalFileName: (f) => f.toLowerCase(), + getCurrentDirectory: () => '/', + getDefaultLibFileName: () => LIB_FILE_NAME, + getDirectories: () => [], + getNewLine: () => '\n', + getSourceFile: (filename, options) => { + let content = files.get(filename) + if (content === void 0) throw Error('missing file') + return ts.createSourceFile(filename, content, options) + }, + readFile: (filename) => files.get(filename), + useCaseSensitiveFileNames: () => false, + writeFile: () => { throw Error('unimplemented') }, + }) +} export function parseFile(sourceFilePath: string): ParsedSourceFile { let source = fs.readFileSync(sourceFilePath).toString('utf-8') @@ -72,51 +70,54 @@ export function parseFile(sourceFilePath: string): ParsedSourceFile { return parseSourceFile(sourceFile) } -let isExportedVariable = (node: ts.Node): node is ts.VariableStatement => - ts.isVariableStatement(node) && !!node.modifiers?.some((_) => _.kind === ts.SyntaxKind.ExportKeyword) - -let isExtensionTerm = (node: ts.Node): node is ts.VariableStatement => +let isDefinitionsVariable = (node: ts.Node): node is ts.VariableStatement => ts.isVariableStatement(node) - && !!node.declarationList.declarations.find((declaration) => declaration.name.getText() === 'extension') + && !!node.declarationList.declarations.find((declaration) => declaration.name.getText() === VarName.Def) -let isExtensionInterface = (node: ts.Node): node is ts.InterfaceDeclaration => - ts.isInterfaceDeclaration(node) && findIdentifier(node)?.getText() === 'Extension' -let isExtensionType = (node: ts.Node): node is ts.TypeAliasDeclaration => - ts.isTypeAliasDeclaration(node) && findIdentifier(node)?.getText() === 'Extension' +let isExtensionsVariable = (node: ts.Node): node is ts.VariableStatement => + ts.isVariableStatement(node) + && !!node.declarationList.declarations.find((declaration) => declaration.name.getText() === VarName.Ext) -let findIdentifier = (node: ts.Node) => node.getChildren().find(ts.isIdentifier) +let isTypeDeclaration = (node: ts.Node): node is ts.InterfaceDeclaration | ts.TypeAliasDeclaration => + (ts.isInterfaceDeclaration(node) && findIdentifier(node)?.getText() === VarName.Type) + || (ts.isTypeAliasDeclaration(node) && findIdentifier(node)?.getText() === VarName.Type) -type ExtensionMetadata = { - start: number - end: number - node: ts.VariableDeclaration -} +let findIdentifier = (node: ts.Node) => node.getChildren().find(ts.isIdentifier) -type ParsedExtensionType = { type: ts.InterfaceDeclaration | ts.TypeAliasDeclaration } -type ParsedExtensionTerm = { term: ts.VariableStatement } -type ParsedExtension = ParsedExtensionType & ParsedExtensionTerm +type ParsedTypesNode = { [VarName.Type]: ts.InterfaceDeclaration | ts.TypeAliasDeclaration } +type ParsedDefinitionsNode = { [VarName.Def]: ts.VariableStatement } +type ParsedExtensionsNode = { [VarName.Ext]: ts.VariableStatement } +type ParsedNodes = + & ParsedTypesNode + & ParsedDefinitionsNode + & ParsedExtensionsNode -export function parseExtensionFile(sourceFilePath: string): { term?: string, type?: string } { +export function parseExtensionFile(sourceFilePath: string): ParsedExtensionFile { let source = fs.readFileSync(sourceFilePath).toString('utf-8') let program = createProgram(source) /* initialize the type checker, otherwise we can't perform a traversal */ void program.getTypeChecker() let sourceFile = program.getSourceFiles()[1] - let nodes: Partial = {} - let out: { term?: string, type?: string } = {} + let nodes: Partial = {} + let out: ParsedExtensionFile = {} + void ts.forEachChild(sourceFile, (node) => { - if (isExtensionType(node)) nodes.type = node - if (isExtensionInterface(node)) nodes.type = node - if (isExtensionTerm(node)) nodes.term = node + if (isTypeDeclaration(node)) nodes[VarName.Type] = node + if (isDefinitionsVariable(node)) nodes[VarName.Def] = node + if (isExtensionsVariable(node)) nodes[VarName.Ext] = node }) - if (nodes.type) { - let text = nodes.type.getText() - out.type = text.slice(text.indexOf('{') + 1, text.lastIndexOf('}')) + if (nodes[VarName.Type]) { + let text = nodes[VarName.Type]!.getText() + out[VarName.Type] = text.slice(text.indexOf('{') + 1, text.lastIndexOf('}')) + } + if (nodes[VarName.Def]) { + let text = nodes[VarName.Def]!.getText() + out[VarName.Def] = text.slice(text.indexOf('{') + 1, text.lastIndexOf('}')) } - if (nodes.term) { - let text = nodes.term.getText() - out.term = text.slice(text.indexOf('{') + 1, text.lastIndexOf('}')) + if (nodes[VarName.Ext]) { + let text = nodes[VarName.Ext]!.getText() + out[VarName.Ext] = text.slice(text.indexOf('{') + 1, text.lastIndexOf('}')) } return out } @@ -151,132 +152,161 @@ export function parseSourceFile(sourceFile: ts.SourceFile): ParsedSourceFile { } }) - let content = sourceFile.getFullText() - let body: string = content.slice(bodyStart).trim() + let body = sourceFile.getFullText().slice(bodyStart).trim() return { imports, body } } let parseTypeMarker = P.seq(P.char('{'), P.spaces, P.string(typesMarker), P.spaces, P.char('}')) -let parseTermMarker = P.seq(P.char('{'), P.spaces, P.string(termsMarker), P.spaces, P.char('}')) +let parseDefinitionsMarker = P.seq(P.char('{'), P.spaces, P.string(definitionsMarker), P.spaces, P.char('}')) +let parseExtensionsMarker = P.seq(P.char('{'), P.spaces, P.string(extensionsMarker), P.spaces, P.char('}')) + +type Splice = { + start: number + end: number + content: string + offset: number + firstLineOffset: number +} +let spliceComparator: Comparator = (l, r) => l.start < r.start ? -1 : r.start < l.start ? 1 : 0 -export function replaceExtensions(source: string, extension: { term?: string, type?: string }) { - let typeMarker = parseTypeMarker.find(source) - let termMarker = parseTermMarker.find(source) - - if (typeMarker === void 0 || termMarker === void 0) throw Error('missing marker') - if (extension.type === void 0 || extension.term === void 0) throw Error('missing parsed extension') - if (!typeMarker.result.success || !termMarker.result.success) throw Error('marker parse failed') - if (!typeMarker.result.value[1] || !termMarker.result.value[1]) throw Error('unknown error') - - let typeIndent = ' '.repeat(Math.max(typeMarker.result.value[1].length - 1, 0)) - // TODO: figure out why you need -3 here - let termIndent = ' '.repeat(Math.max(termMarker.result.value[1].length - 3, 0)) - - if (typeMarker.index < termMarker.index) { - let out = '' - + source.slice(0, typeMarker.index + 1) - // TODO: figure out why indent isn't working here - + extension.type - // + extension.type.split('\n').map((_) => typeIndent + _).join('\n') - + source.slice(typeMarker.result.index - 1, termMarker.index + 1) - + extension.term.split('\n').map((_) => termIndent + _).join('\n') - // + extension.term - + source.slice(termMarker.result.index - 1) - console.log('out', out) - return out - } else { - let out = '' - + source.slice(0, termMarker.index + 1) - + extension.term.split('\n').map((_) => termIndent + _).join('\r') - + source.slice(termMarker.result.index - 1, typeMarker.index + 1) - + extension.type.split('\n').map((_) => typeIndent + _).join('\n') - + source.slice(typeMarker.result.index - 1) - console.log('out', out) - return out - } +function splice1(source: string, x: Splice) { + return '' + + source.slice(0, x.start) + + '\n' + ' '.repeat(x.firstLineOffset) + x.content.split('\n').map((_) => ' '.repeat(x.offset) + _).join().trimStart() + + source.slice(x.end) } -export function createProgram(source: string): ts.Program { - let filename = '/source.ts' - let files = new Map() - files.set(filename, source) - files.set(LIB_FILE_NAME, LIB) - return ts.createProgram( - [filename], { - target: ts.ScriptTarget.ESNext, - module: ts.ModuleKind.ESNext, - strict: true, - noEmit: true, - isolatedModules: true, - types: [], +function splice2(source: string, first: Splice, second: Splice) { + return '' + + source.slice(0, first.start) + + '\n' + ' '.repeat(first.firstLineOffset) + first.content.split('\n').map((_) => ' '.repeat(first.offset) + _).join().trimStart() + + source.slice(first.end, second.start) + + '\n' + ' '.repeat(second.firstLineOffset) + second.content.split('\n').map((_) => ' '.repeat(second.offset) + _).join('\n').trimStart() + + source.slice(second.end) +} + +function splice3(source: string, first: Splice, second: Splice, third: Splice) { + return '' + + source.slice(0, first.start) + + '\n' + ' '.repeat(first.firstLineOffset) + first.content.split('\n').map((_) => ' '.repeat(first.offset) + _).join('\n').trimStart() + + source.slice(first.end, second.start) + + '\n' + ' '.repeat(second.firstLineOffset) + second.content.split('\n').map((_) => ' '.repeat(second.offset) + _).join('\n').trimStart() + + source.slice(second.end, third.start) + + '\n' + ' '.repeat(third.firstLineOffset) + third.content.split('\n').map((_) => ' '.repeat(third.offset) + _).join('\n').trimStart() + + source.slice(third.end) +} + +export function replaceExtensions(source: string, parsedExtensionFile: ParsedExtensionFile) { + let typeMarker = parseTypeMarker.find(source) + let defMarker = parseDefinitionsMarker.find(source) + let extMarker = parseExtensionsMarker.find(source) + + if (typeMarker == null) throw Error(`missing ${VarName.Type} marker`) + if (defMarker == null) throw Error(`missing ${VarName.Def} marker`) + if (extMarker == null) throw Error(`missing ${VarName.Ext} marker`) + + if (parsedExtensionFile[VarName.Type] == null) throw Error(`missing ${VarName.Type} text`) + if (parsedExtensionFile[VarName.Def] == null) throw Error(`missing ${VarName.Def} text`) + if (parsedExtensionFile[VarName.Ext] == null) throw Error(`missing ${VarName.Ext} text`) + + if (!typeMarker.result.success) throw Error(`parse for ${VarName.Type} marker failed`) + if (!defMarker.result.success) throw Error(`parse for ${VarName.Def} marker failed`) + if (!extMarker.result.success) throw Error(`parse for ${VarName.Ext} marker failed`) + + if (!Array_isArray(typeMarker.result.value[1])) throw Error(`unknown error when parsing ${VarName.Type} marker`) + if (!Array_isArray(defMarker.result.value[1])) throw Error(`unknown error when parsing ${VarName.Def} marker`) + if (!Array_isArray(extMarker.result.value[1])) throw Error(`unknown error when parsing ${VarName.Def} marker`) + + let unsortedSplices = [{ + start: typeMarker.index + 1, + end: typeMarker.result.index - 1, + content: parsedExtensionFile[VarName.Type] ?? '', + firstLineOffset: Math.max(typeMarker.result.value[1].length - 1, 0), + offset: Math.max(typeMarker.result.value[1].length - 3, 0), }, { - fileExists: (filename) => files.has(filename), - getCanonicalFileName: (f) => f.toLowerCase(), - getCurrentDirectory: () => '/', - getDefaultLibFileName: () => LIB_FILE_NAME, - getDirectories: () => [], - getNewLine: () => '\n', - getSourceFile: (filename, options) => { - let content = files.get(filename) - if (content === void 0) throw Error('missing file') - return ts.createSourceFile(filename, content, options) - }, - readFile: (filename) => files.get(filename), - useCaseSensitiveFileNames: () => false, - writeFile: () => { throw Error('unimplemented') }, - }) + start: defMarker.index + 1, + end: defMarker.result.index - 1, + content: parsedExtensionFile[VarName.Def] ?? '', + firstLineOffset: Math.max(defMarker.result.value[1].length - 1, 0), + offset: Math.max(defMarker.result.value[1].length - 3, 0), + }, { + start: extMarker.index + 1, + end: extMarker.result.index - 1, + content: parsedExtensionFile[VarName.Ext] ?? '', + firstLineOffset: Math.max(extMarker.result.value[1].length - 1, 0), + offset: Math.max(extMarker.result.value[1].length - 3, 0), + }] as const satisfies Splice[] + + let splices = unsortedSplices.sort(spliceComparator) + + // console.log('splices', splices) + + let out = splice3(source, ...splices) + console.log(out) + return out } -// console.log('\n\nparsedTermsMarker', source.slice(parsedTermsMarker)) -// let terms = !parsedTermsMarker?.success ? void 0 : { -// start: parsedTermsMarker.index - parsedTermsMarker.value[1].length, -// end: parsedTermsMarker.index, -// indentation: Math.max(parsedTermsMarker.value[0].length - 1, 0), -// } +// let typeOffset = Math.max(typeMarker.result.value[1].length - 1, 0) +// let typeOffsetFirstLine = Math.max(typeMarker.result.value[1].length - 1, 0) +// let defOffset = Math.max(defMarker.result.value[1].length - 3, 0) +// let defOffsetFirstLine = Math.max(defMarker.result.value[1].length - 1, 0) +// let extOffset = Math.max(defMarker.result.value[1].length - 3, 0) +// let extOffsetFirstLine = Math.max(extMarker.result.value[1].length - 1, 0) + +// let typeIndent = ' '.repeat(typeOffset) +// let defIndent = ' '.repeat(defOffset) +// let extIndent = ' '.repeat(extOffset) + +// let defIndentFirstLine = ' '.repeat(defOffsetFirstLine) +// let extIndentFirstLine = ' '.repeat(extOffsetFirstLine) -// console.log('extension', extension) -// let types = !parsedTypesMarker?.success ? void 0 : { -// start: parsedTypesMarker.index - parsedTypesMarker.value[1].length, -// end: parsedTypesMarker.index, -// indentation: Math.max(parsedTypesMarker.value[0].length - 1, 0), -// } -// let terms = !parsedTermsMarker?.success ? void 0 : { -// start: parsedTermsMarker.index - parsedTermsMarker.value[1].length, -// end: parsedTermsMarker.index, -// indentation: Math.max(parsedTermsMarker.value[0].length - 1, 0), -// } -// if (types === void 0) throw new Error('expected types') -// if (terms === void 0) { -// console.log(source) -// throw new Error('expected terms') +// /** +// * ORDER: +// * 1. types +// * 2. definitions +// * 3. extensions +// */ +// if (typeMarker.index < defMarker.index && defMarker.index < extMarker.index) { +// return '' +// + source.slice(0, typeMarker.index + 1) +// + extension.type +// + source.slice(typeMarker.result.index - 1, termMarker.index + 1) +// + '\n' + termIndentFirstLine + extension.term.split('\n').map((_) => termIndent + _).join('\n').trimStart() +// + source.slice(termMarker.result.index - 1) // } -// if (types.start < terms.start) return '' -// + source.slice(0, types.start) -// + extensions.map(({ type }, ix) => ix === 0 ? removeQuotes(type ?? '') : ' '.repeat(types.indentation) + removeQuotes(type ?? '')).join('\n') -// + source.slice(types.end, terms.start) -// + extensions.map(({ term }, ix) => ix === 0 ? removeQuotes(term ?? '') : ' '.repeat(terms.indentation) + removeQuotes(term ?? '')).join(',\n') -// + source.slice(terms.end) -// else return '' -// + source.slice(0, terms.start) -// + extensions.map(({ term }, ix) => ix === 0 ? removeQuotes(term ?? '') : ' '.repeat(terms.indentation) + removeQuotes(term ?? '')).join(',\n') -// + source.slice(terms.end, types.start) -// + extensions.map(({ type }, ix) => ix === 0 ? removeQuotes(type ?? '') : ' '.repeat(types.indentation) + removeQuotes(type ?? '')).join('\n') -// + source.slice(types.end) - - -// let removeQuotes = (text: string) => isQuoted(text) ? text.slice(1, -1) : text - -// let parseTypesMarker = P.seq(P.spaces, P.string(typesMarker)).map(([ws, marker]) => [ws?.join('') ?? '', marker] satisfies [any, any]) -// let parseTermsMarker = P.seq(P.spaces, P.string(termsMarker)).map(([ws, marker]) => [ws?.join('') ?? '', marker] satisfies [any, any]) - -// let isQuoted = (text: string) => -// (text.startsWith('"') && text.endsWith('"')) -// || (text.startsWith('`') && text.endsWith('`')) -// || (text.startsWith(`'`) && text.endsWith(`'`)) +// /** +// * ORDER: +// * 1. types +// * 2. extensions +// * 3. definitions +// */ + +// /** +// * ORDER: +// * 1. definitions +// * 2. types +// * 3. extensions +// */ + +// /** +// * ORDER: +// * 1. definitions +// * 2. extensions +// * 3. extensions +// */ + +// else { +// return '' +// + source.slice(0, termMarker.index + 1) +// + '\n' + termIndentFirstLine + extension.term.split('\n').map((_) => termIndent + _).join('\n').trimStart() +// + source.slice(termMarker.result.index - 1, typeMarker.index + 1) +// + extension.type.split('\n').map((_) => typeIndent + _).join('\n') +// + source.slice(typeMarker.result.index - 1) +// } \ No newline at end of file diff --git a/packages/schema-generator/test/e2e.test.ts b/packages/schema-generator/test/e2e.test.ts new file mode 100644 index 00000000..09a1397f --- /dev/null +++ b/packages/schema-generator/test/e2e.test.ts @@ -0,0 +1,333 @@ +import * as vi from 'vitest' + +import * as t from './namespace.js' +import { configure } from '@traversable/schema' +import { mut } from '@traversable/registry' + +vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { + + vi.it('〖️⛳️〗› ❲generated❳: integer schema', () => { + vi.assert.isTrue(t.integer(0)) + vi.assert.isFalse(t.integer(0.1)) + vi.assert.isFalse(t.integer('')) + }) + + vi.it('〖️⛳️〗› ❲generated❳: number schema', () => { + vi.assert.isTrue(t.number(0)) + vi.assert.isTrue(t.number(0.1)) + vi.assert.isFalse(t.number('')) + }) + + vi.it('〖️⛳️〗› ❲generated❳: string schema', () => { + vi.assert.isTrue(t.string('')) + vi.assert.isFalse(t.string(0.1)) + }) + + vi.it('〖️⛳️〗› ❲generated❳: optional schema', () => { + vi.assert.isTrue(t.optional(t.integer)(void 0)) + vi.assert.isTrue(t.optional(t.integer)(0)) + vi.assert.isFalse(t.optional(t.integer)('')) + + vi.assert.isTrue(t.object({ a: t.optional(t.integer) })({})) + vi.assert.isTrue(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({ b: { c: 0 } })) + vi.assert.isTrue(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({ a: 0, b: { c: 1 } })) + vi.assert.isTrue(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({})) + + configure({ schema: { optionalTreatment: 'presentButUndefinedIsOK' } }) + vi.assert.isTrue(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({ b: void 0 })) + + configure({ schema: { optionalTreatment: 'exactOptional' } }) + vi.assert.isFalse(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({ b: void 0 })) + }) + + vi.it('〖️⛳️〗› ❲generated❳: array schema', () => { + vi.assert.isTrue(t.array(t.string)([])) + vi.assert.isTrue(t.array(t.string)([''])) + vi.assert.isFalse(t.array(t.string)({})) + vi.assert.isFalse(t.array(t.string)([0])) + }) + + vi.it('〖️⛳️〗› ❲generated❳: record schema', () => { + vi.assert.isTrue(t.record(t.integer)({})) + vi.assert.isTrue(t.record(t.integer)({ '': 0 })) + vi.assert.isFalse(t.record(t.integer)({ '': false })) + vi.assert.isFalse(t.record(t.integer)([])) + }) + + vi.it('〖️⛳️〗› ❲generated❳: union schema', () => { + vi.assert.isTrue(t.union(t.integer, t.string)(0)) + vi.assert.isTrue(t.union(t.integer, t.string)('')) + vi.assert.isFalse(t.union(t.integer, t.string)(false)) + }) + + vi.it('〖️⛳️〗› ❲generated❳: intersect schema', () => { + vi.assert.isTrue(t.intersect(t.integer)(0)) + vi.assert.isTrue(t.intersect(t.tuple())([])) + vi.assert.isTrue(t.intersect(t.object({}))({})) + vi.assert.isTrue(t.intersect(t.object({ a: t.integer }), t.object({ b: t.string }))({ a: 0, b: '' })) + vi.assert.isFalse(t.intersect(t.object({ a: t.integer }), t.object({ b: t.string }))({})) + vi.assert.isFalse(t.intersect(t.object({ a: t.integer }), t.object({ b: t.string }))({ a: 0 })) + vi.assert.isFalse(t.intersect(t.object({ a: t.integer }), t.object({ b: t.string }))({ b: '' })) + }) + + vi.it('〖️⛳️〗› ❲generated❳: tuple schema', () => { + vi.assert.isTrue(t.tuple()([])) + vi.assert.isTrue(t.tuple(t.integer, t.string)([0, ''])) + vi.assert.isFalse(t.tuple(t.integer, t.string)(['', 0])) + vi.assert.isFalse(t.tuple(t.integer, t.string)([0])) + vi.assert.isFalse(t.tuple(t.integer, t.string)({})) + }) + + vi.it('〖️⛳️〗› ❲generated❳: object schema', () => { + vi.assert.isTrue(t.object({})({})) + vi.assert.isTrue(t.object({ '': t.integer })({ '': 0 })) + vi.assert.isFalse(t.object({ '': t.integer })({})) + vi.assert.isFalse(t.object({})([])) + }) +}) + +vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳: .toString', () => { + + vi.it('〖️⛳️〗› ❲generated❳: integer.toString()', () => { + vi.expect(t.integer.toString()).toMatchInlineSnapshot(`"number"`) + vi.expectTypeOf(t.integer.toString()).toEqualTypeOf('number' as const) + }) + + vi.it('〖️⛳️〗› ❲generated❳: number.toString()', () => { + vi.expect(t.number.toString()).toMatchInlineSnapshot(`"number"`) + vi.expectTypeOf(t.number.toString()).toEqualTypeOf('number' as const) + }) + + vi.it('〖️⛳️〗› ❲generated❳: string.toString()', () => { + vi.expect(t.string.toString()).toMatchInlineSnapshot(`"string"`) + vi.expectTypeOf(t.string.toString()).toEqualTypeOf('string' as const) + }) + + vi.it('〖️⛳️〗› ❲generated❳: array(...).toString()', () => { + vi.expect(t.array(t.string).toString()).toMatchInlineSnapshot(`"(\${string})[]"`) + vi.expect(t.array(t.array(t.string)).toString()).toMatchInlineSnapshot(`"(\${string})[]"`) + vi.expectTypeOf(t.array(t.string).toString()).toEqualTypeOf('(string)[]' as const) + vi.expectTypeOf(t.array(t.array(t.string)).toString()).toEqualTypeOf('((string)[])[]' as const) + }) + + vi.it('〖️⛳️〗› ❲generated❳: record(...).toString()', () => { + vi.expect(t.record(t.string).toString()).toMatchInlineSnapshot(`"Record"`) + vi.expect(t.record(t.record(t.string)).toString()).toMatchInlineSnapshot(`"Record>"`) + vi.expectTypeOf(t.record(t.string).toString()).toEqualTypeOf('Record' as const) + vi.expectTypeOf(t.record(t.array(t.string)).toString()).toEqualTypeOf('Record' as const) + }) + + vi.it('〖️⛳️〗› ❲generated❳: union(...).toString()', () => { + vi.expect(t.union(t.string).toString()).toMatchInlineSnapshot(`"(string)"`) + vi.expect(t.union(t.array(t.string), t.array(t.number)).toString()).toMatchInlineSnapshot(`"((\${string})[] | (\${string})[])"`) + vi.expectTypeOf(t.union().toString()).toEqualTypeOf('never' as const) + vi.expectTypeOf(t.union(t.integer).toString()).toEqualTypeOf('(number)' as const) + vi.expectTypeOf(t.union(t.integer, t.string).toString()).toEqualTypeOf('(number | string)' as const) + vi.expectTypeOf(t.union(t.array(t.string), t.array(t.number)).toString()).toEqualTypeOf('((string)[] | (number)[])' as const) + }) + + vi.it('〖️⛳️〗› ❲generated❳: intersect(...).toString()', () => { + vi.expect(t.intersect(t.string).toString()).toMatchInlineSnapshot(`"(string)"`) + vi.expect(t.intersect(t.intersect(t.string)).toString()).toMatchInlineSnapshot(`"((string))"`) + vi.expectTypeOf(t.intersect().toString()).toEqualTypeOf('unknown' as const) + vi.expectTypeOf(t.intersect(t.integer).toString()).toEqualTypeOf('(number)' as const) + vi.expectTypeOf(t.intersect(t.object({}), t.tuple()).toString()).toEqualTypeOf('({} & [])' as const) + }) + + vi.it('〖️⛳️〗› ❲generated❳: tuple(...).toString()', () => { + vi.expect(t.tuple().toString()).toMatchInlineSnapshot(`"[]"`) + vi.expect(t.tuple(t.tuple(t.number), t.string).toString()).toMatchInlineSnapshot(`"[[number], string]"`) + vi.expectTypeOf(t.tuple().toString()).toEqualTypeOf('[]' as const) + vi.expectTypeOf(t.tuple(t.tuple(t.number), t.tuple(t.string)).toString()).toEqualTypeOf('[[number], [string]]' as const) + }) + + vi.it('〖️⛳️〗› ❲generated❳: object(...).toString()', () => { + vi.expect(t.object({}).toString()).toMatchInlineSnapshot(`"{}"`) + vi.expect(t.object({ a: t.integer }).toString()).toMatchInlineSnapshot(`"{ 'a': number }"`) + vi.expectTypeOf(t.object({}).toString()).toEqualTypeOf('{}' as const) + vi.expectTypeOf(t.object({ a: t.number }).toString()).toEqualTypeOf(`{ 'a': number }` as const) + vi.expectTypeOf(t.object({ a: t.object({ b: t.number }) }).toString()).toEqualTypeOf(`{ 'a': { 'b': number } }` as const) + }) + + vi.it('〖️⛳️〗› ❲generated❳: optional(...).toString()', () => { + vi.expect(t.optional(t.integer).toString()).toMatchInlineSnapshot(`"(number | undefined)"`) + vi.expect(t.optional(t.string).toString()).toMatchInlineSnapshot(`"(string | undefined)"`) + vi.expectTypeOf(t.optional(t.integer).toString()).toEqualTypeOf('(number | undefined)' as const) + vi.expectTypeOf(t.object({ a: t.optional(t.integer) }).toString()).toEqualTypeOf(`{ 'a'?: (number | undefined) }` as const) + vi.expectTypeOf(t.object({ a: t.optional(t.object({ b: t.optional(t.integer) })) }).toString()) + .toEqualTypeOf(`{ 'a'?: ({ 'b'?: (number | undefined) } | undefined) }` as const) + }) +}) + +vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳: .toJsonSchema', () => { + + vi.it('〖️⛳️〗› ❲generated❳: integer.toJsonSchema()', () => { + vi.expectTypeOf(t.integer.toJsonSchema()).toEqualTypeOf(mut({ type: 'integer' })) + vi.expectTypeOf(t.integer.min(0).toJsonSchema()).toEqualTypeOf(mut({ type: 'integer', minimum: 0 })) + vi.expectTypeOf(t.integer.max(0).toJsonSchema()).toEqualTypeOf(mut({ type: 'integer', maximum: 0 })) + vi.expectTypeOf(t.integer.between(0, 1).toJsonSchema()).toEqualTypeOf(mut({ type: 'integer', minimum: 0, maximum: 1 })) + }) + + vi.it('〖️⛳️〗› ❲generated❳: number.toJsonSchema()', () => { + vi.expectTypeOf(t.number.toJsonSchema()).toEqualTypeOf(mut({ type: 'number' })) + vi.expectTypeOf(t.number.min(0).toJsonSchema()).toEqualTypeOf(mut({ type: 'number', minimum: 0, })) + vi.expectTypeOf(t.number.max(0).toJsonSchema()).toEqualTypeOf(mut({ type: 'number', maximum: 0, })) + vi.expectTypeOf(t.number.between(0, 1).toJsonSchema()).toEqualTypeOf(mut({ type: 'number', minimum: 0, maximum: 1, })) + vi.expectTypeOf(t.number.moreThan(0).toJsonSchema()).toEqualTypeOf(mut({ type: 'number', exclusiveMinimum: 0, })) + vi.expectTypeOf(t.number.lessThan(0).toJsonSchema()).toEqualTypeOf(mut({ type: 'number', exclusiveMaximum: 0, })) + }) + + vi.it('〖️⛳️〗› ❲generated❳: string.toJsonSchema()', () => { + vi.expectTypeOf(t.string.toJsonSchema()).toEqualTypeOf(mut({ type: 'string' })) + vi.expectTypeOf(t.string.min(0).toJsonSchema()).toEqualTypeOf(mut({ type: 'string', minLength: 0, })) + vi.expectTypeOf(t.string.max(0).toJsonSchema()).toEqualTypeOf(mut({ type: 'string', maxLength: 0, })) + vi.expectTypeOf(t.string.between(0, 1).toJsonSchema()).toEqualTypeOf(mut({ type: 'string', minLength: 0, maxLength: 1, })) + }) + + vi.it('〖️⛳️〗› ❲generated❳: array(...).toJsonSchema()', () => { + vi.expectTypeOf(t.array(t.integer).toJsonSchema()).toEqualTypeOf<{ type: 'array', items: { type: 'integer' } }>() + vi.expectTypeOf(t.array(t.number).toJsonSchema()).toEqualTypeOf<{ type: 'array', items: { type: 'number' } }>() + vi.expectTypeOf(t.array(t.array(t.integer)).toJsonSchema()).toEqualTypeOf<{ type: 'array', items: { type: 'array', items: { type: 'integer' } } }>() + vi.expectTypeOf(t.array(t.integer).min(0).toJsonSchema()).toEqualTypeOf<{ type: 'array', items: { type: 'integer' }, minLength: 0 }>() + vi.expectTypeOf(t.array(t.integer).max(0).toJsonSchema()).toEqualTypeOf<{ type: 'array', items: { type: 'integer' }, maxLength: 0 }>() + vi.expectTypeOf(t.array(t.integer).between(0, 1).toJsonSchema()).toEqualTypeOf<{ type: 'array', items: { type: 'integer' }, minLength: 0, maxLength: 1 }>() + }) + + vi.it('〖️⛳️〗› ❲generated❳: record(...).toJsonSchema()', () => { + vi.expectTypeOf(t.record(t.integer).toJsonSchema()).toEqualTypeOf<{ type: 'object', additionalProperties: { type: 'integer' } }>() + vi.expectTypeOf( + t.record(t.record(t.integer)).toJsonSchema() + ).toEqualTypeOf<{ + type: 'object' + additionalProperties: { + type: 'object' + additionalProperties: { type: 'integer' } + } + }>() + }) + + vi.it('〖️⛳️〗› ❲generated❳: tuple(...).toJsonSchema()', () => { + vi.expectTypeOf(t.tuple().toJsonSchema()) + .toEqualTypeOf<{ type: 'array', items: [], additionalItems: false, minItems: 0, maxItems: 0 }>() + vi.expectTypeOf(t.tuple(t.integer).toJsonSchema()) + .toEqualTypeOf<{ + type: 'array' + items: [{ type: 'integer' }] + additionalItems: false + minItems: 1 + maxItems: 1 + }>() + }) + + vi.it('〖️⛳️〗› ❲generated❳: object(...).toJsonSchema()', () => { + vi.expectTypeOf(t.object({}).toJsonSchema()).toEqualTypeOf<{ type: 'object', required: [], properties: {} }>() + vi.expectTypeOf(t.object({ a: t.string }).toJsonSchema()) + .toEqualTypeOf<{ type: 'object', required: 'a'[], properties: { a: { type: 'string' } } }>() + vi.expectTypeOf(t.object({ a: t.optional(t.string) }).toJsonSchema()) + .toEqualTypeOf<{ type: 'object', required: [], properties: { a: { type: 'string', nullable: true } } }>() + vi.expectTypeOf(t.object({ a: t.optional(t.string), b: t.integer }).toJsonSchema()) + .toEqualTypeOf<{ type: 'object', required: 'b'[], properties: { a: { type: 'string', nullable: true }, b: { type: 'integer' } } }>() + }) + + vi.it('〖️⛳️〗› ❲generated❳: intersect(...).toJsonSchema()', () => { + vi.expectTypeOf(t.intersect(t.object({})).toJsonSchema()) + .toEqualTypeOf<{ allOf: [{ type: 'object', required: [], properties: {} }] }>() + + vi.expectTypeOf( + t.intersect( + t.object({ a: t.number }), + t.object({ b: t.string }) + ).toJsonSchema() + ) + .toEqualTypeOf<{ + allOf: [ + { type: 'object', required: 'a'[], properties: { a: { type: 'number' } } }, + { type: 'object', required: 'b'[], properties: { b: { type: 'string' } } } + ] + }>() + }) + + vi.it('〖️⛳️〗› ❲generated❳: union(...).toJsonSchema()', () => { + vi.expectTypeOf(t.union(t.object({})).toJsonSchema()) + .toEqualTypeOf<{ anyOf: [{ type: 'object', required: [], properties: {} }] }>() + + vi.expectTypeOf( + t.union( + t.object({ a: t.number }), + t.object({ b: t.string }) + ).toJsonSchema() + ) + .toEqualTypeOf<{ + anyOf: [ + { type: 'object', required: 'a'[], properties: { a: { type: 'number' } } }, + { type: 'object', required: 'b'[], properties: { b: { type: 'string' } } } + ] + }>() + }) + +}) + +vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳: .equals', () => { + + vi.it('〖️⛳️〗› ❲generated❳: tuple(...).equals()', () => { + vi.assert.isTrue(t.tuple(t.integer, t.tuple(t.string)).equals([0, ['']], [0, ['']])) + vi.assert.isFalse(t.tuple(t.integer, t.tuple(t.string)).equals([0, ['']], [1, ['']])) + vi.assert.isFalse(t.tuple(t.integer, t.tuple(t.string)).equals([0, ['']], [0, ['-']])) + }) + + vi.it('〖️⛳️〗› ❲generated❳: object(...).equals()', () => { + vi.assert.isTrue(t.object({ a: t.integer, b: t.object({ c: t.optional(t.string) }) }).equals({ a: 0, b: { c: 'hey' } }, { a: 0, b: { c: 'hey' } })) + vi.assert.isFalse(t.object({ a: t.integer, b: t.object({ c: t.optional(t.string) }) }).equals({ a: 0, b: { c: 'hey' } }, { a: 0, b: { c: 'ho' } })) + }) +}) + +vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳: .validate', () => { + vi.it('〖️⛳️〗› ❲generated❳: object(...).validate()', () => { + vi.expect(t.object({}).validate([])).toMatchInlineSnapshot(` + [ + { + "got": [], + "kind": "TYPE_MISMATCH", + "msg": "Expected object", + "path": [], + }, + ] + `) + }) + + vi.it('〖️⛳️〗› ❲generated❳: object(...).validate()', () => { + + vi.assert.isTrue(t.object({ a: t.integer }).validate({ a: 0 })) + vi.assert.isTrue(t.object({ a: t.optional(t.integer) }).validate({ a: 0 })) + vi.expect(t.object({ a: t.optional(t.integer) }).validate({ a: '' })).toMatchInlineSnapshot(` + [ + { + "expected": "number", + "got": "", + "kind": "TYPE_MISMATCH", + "msg": "Expected an integer", + "path": [ + "a", + ], + }, + ] + `) + + configure({ schema: { optionalTreatment: 'presentButUndefinedIsOK' } }) + vi.assert.isTrue(t.object({ a: t.optional(t.integer) }).validate({ a: void 0 })) + + configure({ schema: { optionalTreatment: 'exactOptional' } }) + vi.expect(t.object({ a: t.optional(t.integer) }).validate({ a: void 0 })).toMatchInlineSnapshot(` + [ + { + "got": undefined, + "kind": "TYPE_MISMATCH", + "msg": "Expected optional", + "path": [ + "a", + ], + }, + ] + `) + }) +}) \ No newline at end of file diff --git a/packages/schema-generator/test/generated.test.ts b/packages/schema-generator/test/generated.test.ts deleted file mode 100644 index b02a647e..00000000 --- a/packages/schema-generator/test/generated.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { tuple } from './__generated__/tuple.gen.js' -import { string } from './__generated__/string.gen.js' -import { number } from './__generated__/number.gen.js' -import { object } from './__generated__/object.gen.js' -import { intersect } from './__generated__/intersect.gen.js' -import { union } from './__generated__/union.gen.js' - -let xs = intersect(string).toJsonSchema() -let ys = tuple(string).toJsonSchema() -let zs = object({ a: number }).toJsonSchema() -let as = object({ a: string }).toJsonSchema() -let bs = union(string).toJsonSchema() diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index 037d9388..306c2fe0 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -9,31 +9,33 @@ import { parseFile, replaceExtensions, writeSchemas, + parseSourceFile, } from '@traversable/schema-generator' import { fn } from '@traversable/registry' let DIR_PATH = path.join(path.resolve(), 'packages', 'schema-generator', 'test') let DATA_PATH = path.join(DIR_PATH, 'test-data') +/** + * ## TODO: + * - [ ] null + * - [ ] void + * - [ ] never + * - [ ] unknown + * - [ ] any + * - [ ] undefined + * - [ ] symbol + * - [ ] boolean + * - [ ] readonlyArray + * - [x] optional + * - [x] bigint + * - [x] eq + */ +let TODOs = void 0 + let PATH = { __generated__: path.join(DIR_PATH, '__generated__'), sources: { - array: { - core: path.join(DATA_PATH, 'array', 'core.ts'), - extension: path.join(DATA_PATH, 'array', 'extension.ts'), - equals: path.join(DATA_PATH, 'array', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'array', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'array', 'toString.ts'), - validate: path.join(DATA_PATH, 'array', 'validate.ts'), - }, - string: { - core: path.join(DATA_PATH, 'string', 'core.ts'), - extension: path.join(DATA_PATH, 'string', 'extension.ts'), - equals: path.join(DATA_PATH, 'string', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'string', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'string', 'toString.ts'), - validate: path.join(DATA_PATH, 'string', 'validate.ts'), - }, integer: { core: path.join(DATA_PATH, 'integer', 'core.ts'), extension: path.join(DATA_PATH, 'integer', 'extension.ts'), @@ -42,6 +44,14 @@ let PATH = { toString: path.join(DATA_PATH, 'integer', 'toString.ts'), validate: path.join(DATA_PATH, 'integer', 'validate.ts'), }, + bigint: { + core: path.join(DATA_PATH, 'bigint', 'core.ts'), + extension: path.join(DATA_PATH, 'bigint', 'extension.ts'), + equals: path.join(DATA_PATH, 'bigint', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'bigint', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'bigint', 'toString.ts'), + validate: path.join(DATA_PATH, 'bigint', 'validate.ts'), + }, number: { core: path.join(DATA_PATH, 'number', 'core.ts'), extension: path.join(DATA_PATH, 'number', 'extension.ts'), @@ -50,6 +60,54 @@ let PATH = { toString: path.join(DATA_PATH, 'number', 'toString.ts'), validate: path.join(DATA_PATH, 'number', 'validate.ts'), }, + string: { + core: path.join(DATA_PATH, 'string', 'core.ts'), + extension: path.join(DATA_PATH, 'string', 'extension.ts'), + equals: path.join(DATA_PATH, 'string', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'string', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'string', 'toString.ts'), + validate: path.join(DATA_PATH, 'string', 'validate.ts'), + }, + eq: { + core: path.join(DATA_PATH, 'eq', 'core.ts'), + extension: path.join(DATA_PATH, 'eq', 'extension.ts'), + equals: path.join(DATA_PATH, 'eq', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'eq', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'eq', 'toString.ts'), + validate: path.join(DATA_PATH, 'eq', 'validate.ts'), + }, + optional: { + core: path.join(DATA_PATH, 'optional', 'core.ts'), + extension: path.join(DATA_PATH, 'optional', 'extension.ts'), + equals: path.join(DATA_PATH, 'optional', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'optional', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'optional', 'toString.ts'), + validate: path.join(DATA_PATH, 'optional', 'validate.ts'), + }, + array: { + core: path.join(DATA_PATH, 'array', 'core.ts'), + extension: path.join(DATA_PATH, 'array', 'extension.ts'), + equals: path.join(DATA_PATH, 'array', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'array', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'array', 'toString.ts'), + validate: path.join(DATA_PATH, 'array', 'validate.ts'), + }, + record: { + core: path.join(DATA_PATH, 'record', 'core.ts'), + extension: path.join(DATA_PATH, 'record', 'extension.ts'), + equals: path.join(DATA_PATH, 'record', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'record', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'record', 'toString.ts'), + validate: path.join(DATA_PATH, 'record', 'validate.ts'), + }, + union: { + core: path.join(DATA_PATH, 'union', 'core.ts'), + extension: path.join(DATA_PATH, 'union', 'extension.ts'), + equals: path.join(DATA_PATH, 'union', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'union', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'union', 'toString.ts'), + validate: path.join(DATA_PATH, 'union', 'validate.ts'), + }, intersect: { core: path.join(DATA_PATH, 'intersect', 'core.ts'), extension: path.join(DATA_PATH, 'intersect', 'extension.ts'), @@ -58,14 +116,6 @@ let PATH = { toString: path.join(DATA_PATH, 'intersect', 'toString.ts'), validate: path.join(DATA_PATH, 'intersect', 'validate.ts'), }, - object: { - core: path.join(DATA_PATH, 'object', 'core.ts'), - extension: path.join(DATA_PATH, 'object', 'extension.ts'), - equals: path.join(DATA_PATH, 'object', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'object', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'object', 'toString.ts'), - validate: path.join(DATA_PATH, 'object', 'validate.ts'), - }, tuple: { core: path.join(DATA_PATH, 'tuple', 'core.ts'), extension: path.join(DATA_PATH, 'tuple', 'extension.ts'), @@ -74,28 +124,32 @@ let PATH = { toString: path.join(DATA_PATH, 'tuple', 'toString.ts'), validate: path.join(DATA_PATH, 'tuple', 'validate.ts'), }, - union: { - core: path.join(DATA_PATH, 'union', 'core.ts'), - extension: path.join(DATA_PATH, 'union', 'extension.ts'), - equals: path.join(DATA_PATH, 'union', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'union', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'union', 'toString.ts'), - validate: path.join(DATA_PATH, 'union', 'validate.ts'), + object: { + core: path.join(DATA_PATH, 'object', 'core.ts'), + extension: path.join(DATA_PATH, 'object', 'extension.ts'), + equals: path.join(DATA_PATH, 'object', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'object', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'object', 'toString.ts'), + validate: path.join(DATA_PATH, 'object', 'validate.ts'), }, }, targets: { - array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), - intersect: path.join(DIR_PATH, '__generated__', 'intersect.gen.ts'), - number: path.join(DIR_PATH, '__generated__', 'number.gen.ts'), integer: path.join(DIR_PATH, '__generated__', 'integer.gen.ts'), - object: path.join(DIR_PATH, '__generated__', 'object.gen.ts'), + bigint: path.join(DIR_PATH, '__generated__', 'bigint.gen.ts'), + number: path.join(DIR_PATH, '__generated__', 'number.gen.ts'), string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), - tuple: path.join(DIR_PATH, '__generated__', 'tuple.gen.ts'), + eq: path.join(DIR_PATH, '__generated__', 'eq.gen.ts'), + optional: path.join(DIR_PATH, '__generated__', 'optional.gen.ts'), + array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), + record: path.join(DIR_PATH, '__generated__', 'record.gen.ts'), union: path.join(DIR_PATH, '__generated__', 'union.gen.ts'), + intersect: path.join(DIR_PATH, '__generated__', 'intersect.gen.ts'), + tuple: path.join(DIR_PATH, '__generated__', 'tuple.gen.ts'), + object: path.join(DIR_PATH, '__generated__', 'object.gen.ts'), } } -vi.describe('〖️⛳️〗‹‹‹ ❲make❳', () => { +vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { vi.it('〖️⛳️〗› ❲makeImport❳', () => { vi.expect( makeImport('@traversable/schema', { term: { named: ['t'], namespace: [] }, type: { named: ['Predicate'], namespace: ['T'] } }).join('\n'), @@ -266,35 +320,34 @@ vi.describe('〖️⛳️〗‹‹‹ ❲make❳', () => { // validate: parseFile(PATH.sources.string.validate), // } - // makeImports({ string: fn.map(src, (file) => file.imports) }) + // vi.expect(makeImports({ string: fn.map(src, (file) => file.imports) })).toMatchInlineSnapshot(` + // { + // "string": "import type { + // Equal, + // Force, + // Integer, + // PickIfDefined, + // Unknown + // } from '@traversable/registry' + // import { + // has, + // Math_max, + // Math_min, + // Object_assign, + // URI + // } from '@traversable/registry' + // import type { Bounds, t } from '@traversable/schema' + // import { __carryover as carryover, __within as within } from '@traversable/schema' + // import type { SizeBounds } from '@traversable/schema-to-json-schema' + // import type { ValidationError, ValidationFn } from '@traversable/derive-validators' + // import { NullaryErrors } from '@traversable/derive-validators'", + // } + // `) }) - - // vi.it('〖️⛳️〗› ❲makeImports❳', () => { - // vi.expect(makeImports({ equals: true, toJsonSchema: true, toString: true, validate: true }, dependencies)).toMatchInlineSnapshot(` - // { - // "array": "import type { t, ValidationError, ValidationFn } from '@traversable/derive-validators' - // import type * as T from '@traversable/registry' - // import { - // Array_isArray, - // has, - // Object_is, - // URI - // } from '@traversable/registry' - // import { t } from '@traversable/schema' - // import type { SizeBounds } from '@traversable/schema-to-json-schema'", - // "string": "import type * as T from '@traversable/registry' - // import type * as U from '@traversable/registry' - // import * as V from '@traversable/registry' - // import { Object_keys, URL, whose } from '@traversable/registry' - // import type { VErr, VFn } from '@traversable/schema' - // import { s, stuff } from '@traversable/schema'", - // } - // `) - // }) }) -vi.describe('〖️⛳️〗‹‹‹ ❲parse❳', () => { - vi.it('〖️⛳️〗› ❲getFileImports❳', () => { +vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { + vi.it('〖️⛳️〗› ❲writeSchemas❳', () => { if (!fs.existsSync(DIR_PATH)) fs.mkdirSync(DIR_PATH) if (!fs.existsSync(DATA_PATH)) fs.mkdirSync(DATA_PATH) if (!fs.existsSync(PATH.__generated__)) fs.mkdirSync(PATH.__generated__) diff --git a/packages/schema-generator/test/namespace.ts b/packages/schema-generator/test/namespace.ts new file mode 100644 index 00000000..c833890f --- /dev/null +++ b/packages/schema-generator/test/namespace.ts @@ -0,0 +1,10 @@ +export { array } from './__generated__/array.gen.js' +export { integer } from './__generated__/integer.gen.js' +export { intersect } from './__generated__/intersect.gen.js' +export { number } from './__generated__/number.gen.js' +export { object } from './__generated__/object.gen.js' +export { optional } from './__generated__/optional.gen.js' +export { record } from './__generated__/record.gen.js' +export { string } from './__generated__/string.gen.js' +export { tuple } from './__generated__/tuple.gen.js' +export { union } from './__generated__/union.gen.js' diff --git a/packages/schema-generator/test/test-data/array/core.ts b/packages/schema-generator/test/test-data/array/core.ts index 7a9ff6a5..4a6ae9a5 100644 --- a/packages/schema-generator/test/test-data/array/core.ts +++ b/packages/schema-generator/test/test-data/array/core.ts @@ -1,10 +1,12 @@ -import type { Unknown } from '@traversable/registry' +import type { Integer, Unknown } from '@traversable/registry' import { Array_isArray, - bindUserDefinitions, + bindUserExtensions, Math_max, Math_min, + Number_isSafeInteger, Object_assign, + safeCoerce, URI, } from '@traversable/registry' @@ -13,24 +15,28 @@ import type { Bounds } from '@traversable/schema' import { t, __carryover as carryover, __within as within } from '@traversable/schema' export interface array extends array.core { - //<%= types %> + //<%= Types %> } export function array(schema: S): array export function array(schema: S): array> -export function array(schema: S): array { return array.def(schema) } +export function array(schema: S): array { + return array.def(schema) +} export namespace array { - export let proto = {} + export let userDefinitions = { + //<%= Definitions %> + } as array export function def(x: S, prev?: array): array export function def(x: S, prev?: unknown): array export function def(x: S, prev?: array): array - export function def(x: S, prev?: unknown): {} { + export function def(x: unknown, prev?: unknown): {} { let userDefinitions: Record = { - //<%= terms %> + //<%= Extensions %> } - const arrayPredicate = t.isPredicate(x) ? array$(x) : Array_isArray - function ArraySchema(src: unknown): src is array['_type'] { return arrayPredicate(src) } + const arrayPredicate = t.isPredicate(x) ? array$(safeCoerce(x)) : Array_isArray + function ArraySchema(src: unknown) { return arrayPredicate(src) } ArraySchema.tag = URI.array ArraySchema.def = x ArraySchema.min = function arrayMin(minLength: Min) { @@ -56,24 +62,24 @@ export namespace array { { minLength, maxLength }, ) } - if (t.has('minLength', t.integer)(prev)) ArraySchema.minLength = prev.minLength - if (t.has('maxLength', t.integer)(prev)) ArraySchema.maxLength = prev.maxLength - Object_assign(ArraySchema, proto) - return Object_assign(ArraySchema, bindUserDefinitions(ArraySchema, userDefinitions)) + if (t.has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength + if (t.has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength + Object_assign(ArraySchema, userDefinitions) + return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userDefinitions)) } } export declare namespace array { interface core { - (u: S[] | Unknown): u is this['_type'] + (u: this['_type'] | Unknown): u is this['_type'] tag: URI.array def: S _type: S['_type' & keyof S][] minLength?: number maxLength?: number - min(minLength: Min): array.Min - max(maxLength: Max): array.Max - between(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> + min>(minLength: Min): array.Min + max>(maxLength: Max): array.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> } type Min = [Self] extends [{ maxLength: number }] @@ -91,7 +97,9 @@ export declare namespace array { type type = never | T } -let array$ = (fn: (u: unknown) => u is T) => (u: unknown): u is T[] => Array_isArray(u) && u.every(fn) +let array$ + : (f: (u: unknown) => u is unknown) => (u: unknown) => u is unknown[] + = (f: (u: unknown) => u is unknown) => (u: unknown): u is unknown[] => Array_isArray(u) && u.every(f) function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array diff --git a/packages/schema-generator/test/test-data/array/equals.ts b/packages/schema-generator/test/test-data/array/equals.ts index 75114960..a0ba5093 100644 --- a/packages/schema-generator/test/test-data/array/equals.ts +++ b/packages/schema-generator/test/test-data/array/equals.ts @@ -1,13 +1,13 @@ -import type * as T from '@traversable/registry' +import type { Equal } from '@traversable/registry' import { has, Array_isArray, Object_is } from '@traversable/registry' import type { t } from '@traversable/schema' -export type equals = never | T.Equal +export type equals = never | Equal -export function equals>(arraySchema: S): equals -export function equals(arraySchema: S): equals -export function equals({ def: { def } }: { def: { def: unknown } }): T.Equal { - let equals = has('equals', (x): x is T.Equal => typeof x === 'function')(def) ? def.equals : Object_is +export function equals(arraySchema: t.array): equals +export function equals(arraySchema: t.array): equals +export function equals({ def }: t.array<{ equals: Equal }>): Equal { + let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is function arrayEquals(l: unknown[], r: unknown[]): boolean { if (Object_is(l, r)) return true if (Array_isArray(l)) { diff --git a/packages/schema-generator/test/test-data/array/extension.ts b/packages/schema-generator/test/test-data/array/extension.ts index de3f543b..da791be3 100644 --- a/packages/schema-generator/test/test-data/array/extension.ts +++ b/packages/schema-generator/test/test-data/array/extension.ts @@ -3,16 +3,18 @@ import { validate } from './validate.js' import { toString } from './toString.js' import { equals } from './equals.js' -export let extension = { - toString, - equals, - toJsonSchema, - validate, -} - -export interface Extension { - toString(): toString +export interface Types { + toString: toString equals: equals - toJsonSchema(): toJsonSchema + toJsonSchema: toJsonSchema validate: validate } + +export let Definitions = {} + +export let Extensions = { + toJsonSchema, + validate, + toString, + equals, +} diff --git a/packages/schema-generator/test/test-data/array/toJsonSchema.ts b/packages/schema-generator/test/test-data/array/toJsonSchema.ts index 5d0db639..5fce8c6b 100644 --- a/packages/schema-generator/test/test-data/array/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/array/toJsonSchema.ts @@ -1,17 +1,17 @@ import type { t } from '@traversable/schema' import type * as T from '@traversable/registry' -import { has } from '@traversable/registry' import type { SizeBounds } from '@traversable/schema-to-json-schema' +import { hasSchema } from '@traversable/schema-to-json-schema' export interface toJsonSchema { (): never | T.Force< - & { type: 'array', items: T.Returns } - & T.PickIfDefined + & { type: 'array', items: T.Returns } + & T.PickIfDefined > } -export function toJsonSchema>(arraySchema: T): toJsonSchema -export function toJsonSchema(arraySchema: T): toJsonSchema +export function toJsonSchema>(arraySchema: T): toJsonSchema +export function toJsonSchema(arraySchema: T): toJsonSchema export function toJsonSchema( { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, ): () => { @@ -21,7 +21,7 @@ export function toJsonSchema( maxLength?: number } { function arrayToJsonSchema() { - let items = has('toJsonSchema', (x) => typeof x === 'function')(def) ? def.toJsonSchema() : def + let items = hasSchema(def) ? def.toJsonSchema() : def let out = { type: 'array' as const, items, diff --git a/packages/schema-generator/test/test-data/array/toString.ts b/packages/schema-generator/test/test-data/array/toString.ts index 7581c0f9..25ecb159 100644 --- a/packages/schema-generator/test/test-data/array/toString.ts +++ b/packages/schema-generator/test/test-data/array/toString.ts @@ -1,11 +1,14 @@ -import type { t } from '@traversable/schema' +import { t } from '@traversable/schema' -/* @ts-expect-error */ -export type toString = never | `(${ReturnType})[]` -export function toString>(arraySchema: S): () => toString -export function toString(arraySchema: S): () => toString -export function toString({ def: { def } }: { def: { def: unknown } }) { - return () => { +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType})[]` +} + +export function toString(arraySchema: t.array): toString +export function toString(arraySchema: t.array): toString +export function toString({ def }: { def: unknown }) { + function arrayToString() { let body = ( !!def && typeof def === 'object' @@ -15,4 +18,5 @@ export function toString({ def: { def } }: { def: { def: unknown } }) { : '${string}' return ('(' + body + ')[]') } + return arrayToString } diff --git a/packages/schema-generator/test/test-data/array/validate.ts b/packages/schema-generator/test/test-data/array/validate.ts index 3ce76660..0c1438d4 100644 --- a/packages/schema-generator/test/test-data/array/validate.ts +++ b/packages/schema-generator/test/test-data/array/validate.ts @@ -1,31 +1,16 @@ -import type { t } from '@traversable/schema' -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' import { URI } from '@traversable/registry' +import type { t } from '@traversable/schema' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' import { Errors, NullaryErrors } from '@traversable/derive-validators' export type validate = never | ValidationFn -export function validate>( - arraySchema: S, - bounds?: { minLength?: number, maxLength?: number } -): validate -export function validate( - arraySchema: S, - bounds?: { minLength?: number, maxLength?: number } -): validate -// +export function validate(arraySchema: t.array): validate +export function validate(arraySchema: t.array): validate export function validate( - { def: { def } }: { def: { def: unknown } }, - { minLength, maxLength }: { minLength?: number, maxLength?: number } = {} -): ValidationFn { - let validate = (( - !!def - && typeof def === 'object' - && 'validate' in def - && typeof def.validate === 'function' - ) ? def.validate - : () => true) + { def: { validate = () => true }, minLength, maxLength }: t.array +) { validateArray.tag = URI.array - function validateArray(u: unknown, path: (keyof any)[] = []) { + function validateArray(u: unknown, path = Array.of()) { if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] let errors = Array.of() if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) diff --git a/packages/schema-generator/test/test-data/bigint/core.ts b/packages/schema-generator/test/test-data/bigint/core.ts new file mode 100644 index 00000000..040c8f59 --- /dev/null +++ b/packages/schema-generator/test/test-data/bigint/core.ts @@ -0,0 +1,98 @@ +import type { Unknown } from '@traversable/registry' +import { + Object_assign, + URI, + bindUserExtensions, +} from '@traversable/registry' + +import type { Bounds } from '@traversable/schema' +import { __carryover as carryover, __withinBig as within } from '@traversable/schema' + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface bigint_ extends bigint_.core { + //<%= Types %> +} + +export { bigint_ as bigint } + +function BigIntSchema(src: unknown) { return typeof src === 'bigint' } +BigIntSchema.tag = URI.bigint +BigIntSchema.def = 0n + +const bigint_ = Object_assign( + BigIntSchema, + userDefinitions, +) as bigint_ + +bigint_.min = function bigIntMin(minimum) { + return Object_assign( + boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +bigint_.max = function bigIntMax(maximum) { + return Object_assign( + boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +bigint_.between = function bigIntBetween( + min, + max, + minimum = (max < min ? max : min), + maximum = (max < min ? min : max), +) { + return Object_assign( + boundedBigInt({ gte: minimum, lte: maximum }), + { minimum, maximum } + ) +} + +Object_assign( + bigint_, + bindUserExtensions(bigint_, userExtensions), +) + +declare namespace bigint_ { + interface core extends bigint_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: bigint + tag: URI.bigint + def: this['_type'] + minimum?: bigint + maximum?: bigint + } + type Min + = [Self] extends [{ maximum: bigint }] + ? bigint_.between<[min: X, max: Self['maximum']]> + : bigint_.min + ; + type Max + = [Self] extends [{ minimum: bigint }] + ? bigint_.between<[min: Self['minimum'], max: X]> + : bigint_.max + ; + interface methods { + min(minimum: Min): bigint_.Min + max(maximum: Max): bigint_.Max + between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> + } + interface min extends bigint_ { minimum: Min } + interface max extends bigint_ { maximum: Max } + interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } +} + +function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedBigIntSchema(u: unknown) { + return bigint_(u) && within(bounds)(u) + }, carry, bigint_) +} diff --git a/packages/schema-generator/test/test-data/bigint/equals.ts b/packages/schema-generator/test/test-data/bigint/equals.ts new file mode 100644 index 00000000..3f38a8a5 --- /dev/null +++ b/packages/schema-generator/test/test-data/bigint/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: bigint, right: bigint): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/test/test-data/bigint/extension.ts b/packages/schema-generator/test/test-data/bigint/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/test/test-data/bigint/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/test/test-data/bigint/toJsonSchema.ts b/packages/schema-generator/test/test-data/bigint/toJsonSchema.ts new file mode 100644 index 00000000..8aa548e2 --- /dev/null +++ b/packages/schema-generator/test/test-data/bigint/toJsonSchema.ts @@ -0,0 +1,9 @@ +import type { t } from '@traversable/schema' + +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function bigintToJsonSchema(): void { + return void 0 + } + return bigintToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/bigint/toString.ts b/packages/schema-generator/test/test-data/bigint/toString.ts new file mode 100644 index 00000000..793c903e --- /dev/null +++ b/packages/schema-generator/test/test-data/bigint/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'bigint' } +export function toString(): 'bigint' { return 'bigint' } diff --git a/packages/schema-generator/test/test-data/bigint/validate.ts b/packages/schema-generator/test/test-data/bigint/validate.ts new file mode 100644 index 00000000..a25cad0b --- /dev/null +++ b/packages/schema-generator/test/test-data/bigint/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(bigIntSchema: S): validate { + validateBigInt.tag = URI.bigint + function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { + return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] + } + return validateBigInt +} diff --git a/packages/schema-generator/test/test-data/eq/core.ts b/packages/schema-generator/test/test-data/eq/core.ts new file mode 100644 index 00000000..535d4eb1 --- /dev/null +++ b/packages/schema-generator/test/test-data/eq/core.ts @@ -0,0 +1,40 @@ +import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' +import { applyOptions, bindUserExtensions, Object_assign, URI } from '@traversable/registry' +import { t } from '@traversable/schema' + +export interface eq extends eq.core { + //<%= Types %> +} + +export function eq>(value: V, options?: Options): eq> +export function eq(value: V, options?: Options): eq +export function eq(value: V, options?: Options): eq { + return eq.def(value, options) +} +export namespace eq { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(value: T, options?: Options): eq + export function def(x: T, $?: Options): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const options = applyOptions($) + const eqGuard = t.isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return eqGuard(src) } + EqSchema.tag = URI.tag + EqSchema.def = x + Object_assign(EqSchema, eq.userDefinitions) + return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) + } +} + +export declare namespace eq { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.eq + _type: V + def: V + } +} diff --git a/packages/schema-generator/test/test-data/eq/equals.ts b/packages/schema-generator/test/test-data/eq/equals.ts new file mode 100644 index 00000000..7ffeaf29 --- /dev/null +++ b/packages/schema-generator/test/test-data/eq/equals.ts @@ -0,0 +1,8 @@ +import type { Equal } from '@traversable/registry' +import { t } from '@traversable/schema' + +export type equals = never | Equal +export function equals(eqSchema: t.eq): equals +export function equals(): Equal { + return (left: unknown, right: unknown) => t.eq(left)(right) +} diff --git a/packages/schema-generator/test/test-data/eq/extension.ts b/packages/schema-generator/test/test-data/eq/extension.ts new file mode 100644 index 00000000..0e7c4478 --- /dev/null +++ b/packages/schema-generator/test/test-data/eq/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} + diff --git a/packages/schema-generator/test/test-data/eq/toJsonSchema.ts b/packages/schema-generator/test/test-data/eq/toJsonSchema.ts new file mode 100644 index 00000000..4c998ae9 --- /dev/null +++ b/packages/schema-generator/test/test-data/eq/toJsonSchema.ts @@ -0,0 +1,8 @@ +import type { t } from '@traversable/schema' + +export interface toJsonSchema { (): { const: T } } +export function toJsonSchema(eqSchema: t.eq): toJsonSchema +export function toJsonSchema({ def }: t.eq) { + function eqToJsonSchema() { return { const: def } } + return eqToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/eq/toString.ts b/packages/schema-generator/test/test-data/eq/toString.ts new file mode 100644 index 00000000..36fbaef1 --- /dev/null +++ b/packages/schema-generator/test/test-data/eq/toString.ts @@ -0,0 +1,17 @@ +import type { Key } from '@traversable/registry' +import type { t } from '@traversable/schema' +import { stringify } from '@traversable/schema-to-string' + +export interface toString { + (): [Key] extends [never] + ? [T] extends [symbol] ? 'symbol' : 'symbol' + : [T] extends [string] ? `'${T}'` : Key +} + +export function toString(eqSchema: t.eq): toString +export function toString({ def }: t.eq): () => string { + function eqToString(): string { + return typeof def === 'symbol' ? 'symbol' : stringify(def) + } + return eqToString +} diff --git a/packages/schema-generator/test/test-data/eq/validate.ts b/packages/schema-generator/test/test-data/eq/validate.ts new file mode 100644 index 00000000..ddb3f992 --- /dev/null +++ b/packages/schema-generator/test/test-data/eq/validate.ts @@ -0,0 +1,17 @@ +import { Equal, getConfig, URI } from '@traversable/registry' +import { t } from '@traversable/schema' +import type { Validate } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + +export type validate = Validate +export function validate(eqSchema: t.eq): validate +export function validate({ def }: t.eq): validate { + validateEq.tag = URI.eq + function validateEq(u: unknown, path = Array.of()) { + let options = getConfig().schema + let equals = options?.eq?.equalsFn || Equal.lax + if (equals(def, u)) return true + else return [Errors.eq(u, path, def)] + } + return validateEq +} diff --git a/packages/schema-generator/test/test-data/integer/core.ts b/packages/schema-generator/test/test-data/integer/core.ts index 2b798c86..ade6e720 100644 --- a/packages/schema-generator/test/test-data/integer/core.ts +++ b/packages/schema-generator/test/test-data/integer/core.ts @@ -1,57 +1,38 @@ -import type { Integer } from '@traversable/registry' +import type { Integer, Unknown } from '@traversable/registry' import { Math_min, Math_max, Number_isSafeInteger, Object_assign, URI, + bindUserExtensions, } from '@traversable/registry' import type { Bounds } from '@traversable/schema' import { __carryover as carryover, __within as within } from '@traversable/schema' -export let userDefinitions = { - //<%= terms %> +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> } export { integer } interface integer extends integer.core { - //<%= types %> + //<%= Types %> } -declare namespace integer { - interface core extends integer.methods { - (u: unknown): u is this['_type'] - _type: number - tag: URI.integer - def: this['_type'] - minimum?: number - maximum?: number - } - interface methods { - min>(minimum: Min): integer.Min - max>(maximum: Max): integer.Max - between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ maximum: number }] - ? integer.between<[min: X, max: Self['maximum']]> - : integer.min - type Max - = [Self] extends [{ minimum: number }] - ? integer.between<[min: Self['minimum'], max: X]> - : integer.max - interface min extends integer { minimum: Min } - interface max extends integer { maximum: Max } - interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } -} +function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } +IntegerSchema.tag = URI.integer +IntegerSchema.def = 0 + const integer = Object_assign( - function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) }, + IntegerSchema, userDefinitions, ) as integer -integer.tag = URI.integer -integer.def = 0 integer.min = function integerMin(minimum) { return Object_assign( boundedInteger({ gte: minimum }, carryover(this, 'minimum')), @@ -76,6 +57,38 @@ integer.between = function integerBetween( ) } +Object_assign( + integer, + bindUserExtensions(integer, userExtensions), +) + +declare namespace integer { + interface core extends integer.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.integer + def: this['_type'] + minimum?: number + maximum?: number + } + interface methods { + min>(minimum: Min): integer.Min + max>(maximum: Max): integer.Max + between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maximum: number }] + ? integer.between<[min: X, max: Self['maximum']]> + : integer.min + type Max + = [Self] extends [{ minimum: number }] + ? integer.between<[min: Self['minimum'], max: X]> + : integer.max + interface min extends integer { minimum: Min } + interface max extends integer { maximum: Max } + interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } +} + function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer function boundedInteger(bounds: Bounds, carry?: {}): {} { diff --git a/packages/schema-generator/test/test-data/integer/extension.ts b/packages/schema-generator/test/test-data/integer/extension.ts index 0b36c0f8..c0990a36 100644 --- a/packages/schema-generator/test/test-data/integer/extension.ts +++ b/packages/schema-generator/test/test-data/integer/extension.ts @@ -3,16 +3,19 @@ import { validate } from './validate.js' import { toString } from './toString.js' import { equals } from './equals.js' -export interface Extension { - toString: toString +export interface Types { equals: equals toJsonSchema: toJsonSchema + toString: toString validate: validate } -export let extension = { - toString, +export let Definitions = { equals, + toString, +} + +export let Extensions = { toJsonSchema, validate, } diff --git a/packages/schema-generator/test/test-data/integer/validate.ts b/packages/schema-generator/test/test-data/integer/validate.ts index 21383d41..c3e993fd 100644 --- a/packages/schema-generator/test/test-data/integer/validate.ts +++ b/packages/schema-generator/test/test-data/integer/validate.ts @@ -6,7 +6,7 @@ import { NullaryErrors } from '@traversable/derive-validators' export type validate = ValidationFn export function validate(integerSchema: S): validate { validateInteger.tag = URI.integer - function validateInteger(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { return integerSchema(u) || [NullaryErrors.integer(u, path)] } return validateInteger diff --git a/packages/schema-generator/test/test-data/intersect/core.ts b/packages/schema-generator/test/test-data/intersect/core.ts index 5dc88b73..f1a34487 100644 --- a/packages/schema-generator/test/test-data/intersect/core.ts +++ b/packages/schema-generator/test/test-data/intersect/core.ts @@ -1,37 +1,39 @@ - -import { bindUserDefinitions, Object_assign, URI } from '@traversable/registry' +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, Object_assign, safeCoerce, URI } from '@traversable/registry' import type { IntersectType } from '@traversable/schema' import { t, Predicate } from '@traversable/schema' export interface intersect extends intersect.core { - //<%= types %> + //<%= Types %> } export function intersect(...schemas: S): intersect export function intersect }>(...schemas: S): intersect -export function intersect(...schemas: S) { +export function intersect(...schemas: readonly unknown[]) { return intersect.def(schemas) } export namespace intersect { - export let proto = {} as intersect + export let userDefinitions: Record = { + //<%= Definitions %> + } as intersect export function def(xs: readonly [...T]): intersect - export function def(xs: readonly [...T]): {} { - let userDefinitions = { - //<%= terms %> + export function def(xs: readonly unknown[]): {} { + let userExtensions: Record = { + //<%= Extensions %> } - const allOf = xs.every(t.isPredicate) ? Predicate.is.intersect(xs) : Predicate.is.unknown + const allOf = xs.every(t.isPredicate) ? Predicate.is.intersect(xs.map(safeCoerce)) : Predicate.is.unknown function IntersectSchema(src: unknown) { return allOf(src) } IntersectSchema.tag = URI.intersect IntersectSchema.def = xs - Object_assign(IntersectSchema, intersect.proto) - return Object_assign(IntersectSchema, bindUserDefinitions(IntersectSchema, userDefinitions)) + Object_assign(IntersectSchema, intersect.userDefinitions) + return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) } } export declare namespace intersect { interface core { - (u: unknown): u is this['_type'] + (u: this['_type'] | Unknown): u is this['_type'] tag: URI.intersect def: S _type: IntersectType diff --git a/packages/schema-generator/test/test-data/intersect/extension.ts b/packages/schema-generator/test/test-data/intersect/extension.ts index a78cd352..0927d4dd 100644 --- a/packages/schema-generator/test/test-data/intersect/extension.ts +++ b/packages/schema-generator/test/test-data/intersect/extension.ts @@ -3,18 +3,18 @@ import { toJsonSchema } from './toJsonSchema.js' import { toString } from './toString.js' import { validate } from './validate.js' -export interface Extension { +export interface Types { + toString: toString equals: equals toJsonSchema: toJsonSchema - toString: toString validate: validate } -export let extension = { - equals, +export let Definitions = {} + +export let Extensions = { toJsonSchema, - toString, validate, + toString, + equals, } - -declare const xs: Extension diff --git a/packages/schema-generator/test/test-data/intersect/toString.ts b/packages/schema-generator/test/test-data/intersect/toString.ts index d1facc45..d5c62fb3 100644 --- a/packages/schema-generator/test/test-data/intersect/toString.ts +++ b/packages/schema-generator/test/test-data/intersect/toString.ts @@ -4,7 +4,7 @@ import { t } from '@traversable/schema' import { callToString } from '@traversable/schema-to-string' export interface toString { - (): never | [T] extends [readonly []] ? 'never' + (): never | [T] extends [readonly []] ? 'unknown' /* @ts-expect-error */ : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` } diff --git a/packages/schema-generator/test/test-data/intersect/validate.ts b/packages/schema-generator/test/test-data/intersect/validate.ts index 6237a2da..04695896 100644 --- a/packages/schema-generator/test/test-data/intersect/validate.ts +++ b/packages/schema-generator/test/test-data/intersect/validate.ts @@ -8,7 +8,7 @@ export function validate(intersectSchema: t.inte export function validate(intersectSchema: t.intersect): validate export function validate({ def }: t.intersect) { validateIntersect.tag = URI.intersect - function validateIntersect(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { let errors = Array.of() for (let i = 0; i < def.length; i++) { let results = def[i].validate(u, path) diff --git a/packages/schema-generator/test/test-data/number/core.ts b/packages/schema-generator/test/test-data/number/core.ts index 75468d9c..04dc9e01 100644 --- a/packages/schema-generator/test/test-data/number/core.ts +++ b/packages/schema-generator/test/test-data/number/core.ts @@ -1,25 +1,32 @@ -import { Math_min, Math_max, Object_assign, URI } from '@traversable/registry' +import type { Unknown } from '@traversable/registry' +import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' import type { Bounds } from '@traversable/schema' import { __carryover as carryover, __within as within } from '@traversable/schema' -export let userDefinitions = { - //<%= terms %> -} - export { number_ as number } interface number_ extends number_.core { - //<%= types %> + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> } +export let userExtensions: Record = { + //<%= Extensions %> +} + +function NumberSchema(src: unknown) { return typeof src === 'number' } +NumberSchema.tag = URI.number +NumberSchema.def = 0 + const number_ = Object_assign( - function NumberSchema(src: unknown) { return typeof src === 'number' }, + NumberSchema, userDefinitions, ) as number_ -number_.tag = URI.number -number_.def = 0 number_.min = function numberMin(minimum) { return Object_assign( boundedNumber({ gte: minimum }, carryover(this, 'minimum')), @@ -56,9 +63,14 @@ number_.between = function numberBetween( ) } +Object_assign( + number_, + bindUserExtensions(number_, userExtensions), +) + declare namespace number_ { interface core extends number_.methods { - (u: unknown): u is number + (u: this['_type'] | Unknown): u is this['_type'] _type: number tag: URI.number def: this['_type'] diff --git a/packages/schema-generator/test/test-data/number/extension.ts b/packages/schema-generator/test/test-data/number/extension.ts index 0b36c0f8..81c311ce 100644 --- a/packages/schema-generator/test/test-data/number/extension.ts +++ b/packages/schema-generator/test/test-data/number/extension.ts @@ -3,16 +3,19 @@ import { validate } from './validate.js' import { toString } from './toString.js' import { equals } from './equals.js' -export interface Extension { +export interface Types { toString: toString equals: equals toJsonSchema: toJsonSchema validate: validate } -export let extension = { +export let Definitions = { toString, equals, +} + +export let Extensions = { toJsonSchema, validate, } diff --git a/packages/schema-generator/test/test-data/object/core.ts b/packages/schema-generator/test/test-data/object/core.ts index 3a80c1c5..af07bbfd 100644 --- a/packages/schema-generator/test/test-data/object/core.ts +++ b/packages/schema-generator/test/test-data/object/core.ts @@ -1,19 +1,19 @@ -import type { Force } from '@traversable/registry' +import type { Force, Unknown } from '@traversable/registry' import { Array_isArray, applyOptions, - bindUserDefinitions, + bindUserExtensions, map, Object_assign, Object_keys, - replaceBooleanConstructor, + safeCoerce, URI, } from '@traversable/registry' import type { SchemaOptions as Options } from '@traversable/schema' import { t, Predicate } from '@traversable/schema' interface object_ extends object_.core { - //<%= types %> + //<%= Types %> } export { object_ as object } @@ -25,42 +25,42 @@ function object_< S extends { [x: string]: t.Predicate }, T extends { [K in keyof S]: t.Entry } >(schemas: S, options?: Options): object_ -function object_(schemas: S, options?: Options) { +function object_(schemas: { [x: string]: t.Schema }, options?: Options) { return object_.def(schemas, options) } -const replaceBoolean = replaceBooleanConstructor(t.nonnullable) - namespace object_ { - export let proto = {} as object_ + export let userDefinitions: Record = { + //<%= Definitions %> + } as object_ export function def(xs: T, $?: Options, opt?: string[]): object_ export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { - let userDefinitions: Record = { - //<%= terms %> + let userExtensions: Record = { + //<%= Extensions %> } const keys = Object_keys(xs) const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => t.optional.is(xs[k])) const req = keys.filter((k) => !t.optional.is(xs[k])) const objectGuard = !Predicate.record$(t.isPredicate)(xs) ? Predicate.is.anyObject - : Predicate.is.object(map(xs, replaceBoolean), applyOptions($)) + : Predicate.is.object(map(xs, safeCoerce), applyOptions($)) function ObjectSchema(src: unknown) { return objectGuard(src) } ObjectSchema.tag = URI.object ObjectSchema.def = xs ObjectSchema.opt = opt ObjectSchema.req = req - Object_assign(ObjectSchema, proto) - return Object_assign(ObjectSchema, bindUserDefinitions(ObjectSchema, userDefinitions)) + Object_assign(ObjectSchema, userDefinitions) + return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) } } declare namespace object_ { interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: object_.type tag: URI.object def: S opt: t.Optional - req: Required - _type: object_.type - (u: unknown): u is this['_type'] + req: t.Required } type type< S, diff --git a/packages/schema-generator/test/test-data/object/extension.ts b/packages/schema-generator/test/test-data/object/extension.ts index 50a1fa22..ee871114 100644 --- a/packages/schema-generator/test/test-data/object/extension.ts +++ b/packages/schema-generator/test/test-data/object/extension.ts @@ -3,16 +3,18 @@ import { toJsonSchema } from './toJsonSchema.js' import { toString } from './toString.js' import { validate } from './validate.js' -export let extension = { - equals, - toJsonSchema, - toString, - validate, -} - -export interface Extension { +export interface Types { equals: equals toJsonSchema: toJsonSchema toString: toString validate: validate } + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/test/test-data/object/toString.ts b/packages/schema-generator/test/test-data/object/toString.ts index 9e8680b1..0cbbdc3d 100644 --- a/packages/schema-generator/test/test-data/object/toString.ts +++ b/packages/schema-generator/test/test-data/object/toString.ts @@ -23,16 +23,19 @@ export interface toString> : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` } - export function toString>(objectSchema: t.object): toString export function toString({ def }: t.object) { - if (!!def && typeof def === 'object') { - const entries = Object.entries(def) - if (entries.length === 0) return '{}' - else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" - }: ${hasToString(x) ? x.toString() : 'unknown' - }`).join(', ') - } }` + function objectToString() { + if (!!def && typeof def === 'object') { + const entries = Object.entries(def) + if (entries.length === 0) return '{}' + else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" + }: ${hasToString(x) ? x.toString() : 'unknown' + }`).join(', ') + } }` + } + else return '{ [x: string]: unknown }' } - else return '{ [x: string]: unknown }' + + return objectToString } diff --git a/packages/schema-generator/test/test-data/object/validate.ts b/packages/schema-generator/test/test-data/object/validate.ts index 2b806108..d66ea624 100644 --- a/packages/schema-generator/test/test-data/object/validate.ts +++ b/packages/schema-generator/test/test-data/object/validate.ts @@ -27,44 +27,49 @@ export function validate(objectSchema: t.o export function validate(objectSchema: t.object): validate export function validate(objectSchema: t.object<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { validateObject.tag = URI.object - function validateObject(u: unknown, path: (keyof any)[] = []) { - if (!isObject(u)) return [Errors.object(u, path)] + function validateObject(u: unknown, path_ = Array.of()) { + // if (objectSchema(u)) return true + if (!isObject(u)) return [Errors.object(u, path_)] let errors = Array.of() let { schema: { optionalTreatment } } = getConfig() let keys = Object_keys(objectSchema.def) if (optionalTreatment === 'exactOptional') { for (let i = 0, len = keys.length; i < len; i++) { let k = keys[i] - let path_ = [...path, k] + let path = [...path_, k] if (Object_hasOwn(u, k) && u[k] === undefined) { if (isOptional(objectSchema.def[k].validate)) { let tag = typeName(objectSchema.def[k].validate) if (isKeyOf(tag, NullaryErrors)) { - let args = [u[k], path_, tag] as never as [unknown, (keyof any)[]] + let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] errors.push(NullaryErrors[tag](...args)) } else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path_)) + errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) } } - let results = objectSchema.def[k].validate(u[k], path_) + let results = objectSchema.def[k].validate(u[k], path) if (results === true) continue let tag = typeName(objectSchema.def[k].validate) if (isKeyOf(tag, NullaryErrors)) { - errors.push(NullaryErrors[tag](u[k], path_, tag)) + errors.push(NullaryErrors[tag](u[k], path, tag)) } else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag].invalid(u[k], path_)) + errors.push(UnaryErrors[tag].invalid(u[k], path)) } errors.push(...results) } else if (Object_hasOwn(u, k)) { - let results = objectSchema.def[k].validate(u[k], path_) + let results = objectSchema.def[k].validate(u[k], path) if (results === true) continue + + console.log('results', objectSchema.def[k].validate) + console.log('results', results) + errors.push(...results) continue } else { - errors.push(UnaryErrors.object.missing(u, path_)) + errors.push(UnaryErrors.object.missing(u, path)) continue } } @@ -72,17 +77,17 @@ export function validate(objectSchema: t.object<{ [x: string]: Validator }>): va else { for (let i = 0, len = keys.length; i < len; i++) { let k = keys[i] - let path_ = [...path, k] + let path = [...path_, k] if (!Object_hasOwn(u, k)) { if (!isOptional(objectSchema.def[k].validate)) { - errors.push(UnaryErrors.object.missing(u, path_)) + errors.push(UnaryErrors.object.missing(u, path)) continue } else { if (!Object_hasOwn(u, k)) continue if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { if (u[k] === undefined) continue - let results = objectSchema.def[k].validate(u[k], path_) + let results = objectSchema.def[k].validate(u[k], path) if (results === true) continue for (let j = 0; j < results.length; j++) { let result = results[j] @@ -92,7 +97,7 @@ export function validate(objectSchema: t.object<{ [x: string]: Validator }>): va } } } - let results = objectSchema.def[k].validate(u[k], path_) + let results = objectSchema.def[k].validate(u[k], path) if (results === true) continue for (let l = 0; l < results.length; l++) { let result = results[l] diff --git a/packages/schema-generator/test/test-data/optional/core.ts b/packages/schema-generator/test/test-data/optional/core.ts new file mode 100644 index 00000000..e1263610 --- /dev/null +++ b/packages/schema-generator/test/test-data/optional/core.ts @@ -0,0 +1,43 @@ +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, has, Object_assign, safeCoerce, symbol, URI } from '@traversable/registry' +import { t, Predicate } from '@traversable/schema' + +export interface optional extends optional.core { + //<%= Types %> +} + +export function optional(schema: S): optional +export function optional(schema: S): optional> +export function optional(schema: S): optional { return optional.def(schema) } +export namespace optional { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): optional + export function def(x: T) { + let userExtensions: Record = { + //<%= Extensions %> + } + const optionalGuard = t.isPredicate(x) ? Predicate.is.optional(safeCoerce(x)) : (_: unknown) => true + function OptionalSchema(src: unknown) { return optionalGuard(src) } + OptionalSchema.tag = URI.optional + OptionalSchema.def = x + OptionalSchema[symbol.optional] = 1 + Object_assign(OptionalSchema, optional.userDefinitions) + return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) + } + export const is + : (u: unknown) => u is optional + = has('tag', (u) => u === URI.optional) as never +} + +export declare namespace optional { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.optional + def: S + [symbol.optional]: number + _type: undefined | S['_type' & keyof S] + } + export type type = never | T +} diff --git a/packages/schema-generator/test/test-data/optional/equals.ts b/packages/schema-generator/test/test-data/optional/equals.ts new file mode 100644 index 00000000..11b1d9ac --- /dev/null +++ b/packages/schema-generator/test/test-data/optional/equals.ts @@ -0,0 +1,13 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema' + +export type equals = never | Equal +export function equals(optionalSchema: t.optional): equals +export function equals(optionalSchema: t.optional): equals +export function equals({ def }: t.optional<{ equals: Equal }>): Equal { + return function optionalEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + return def.equals(l, r) + } +} diff --git a/packages/schema-generator/test/test-data/optional/extension.ts b/packages/schema-generator/test/test-data/optional/extension.ts new file mode 100644 index 00000000..0e7c4478 --- /dev/null +++ b/packages/schema-generator/test/test-data/optional/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} + diff --git a/packages/schema-generator/test/test-data/optional/toJsonSchema.ts b/packages/schema-generator/test/test-data/optional/toJsonSchema.ts new file mode 100644 index 00000000..6009f506 --- /dev/null +++ b/packages/schema-generator/test/test-data/optional/toJsonSchema.ts @@ -0,0 +1,19 @@ +import type { Force } from '@traversable/registry' +import type { Returns } from '@traversable/registry' +import { symbol } from '@traversable/registry' +import type { t } from '@traversable/schema' +import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' + +type Nullable = Force + +export interface toJsonSchema { + (): Nullable> + [symbol.optional]: number +} + +export function toJsonSchema(optionalSchema: t.optional): toJsonSchema +export function toJsonSchema({ def }: t.optional) { + function optionalToJsonSchema() { return getSchema(def) } + optionalToJsonSchema[symbol.optional] = wrapOptional(def) + return optionalToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/optional/toString.ts b/packages/schema-generator/test/test-data/optional/toString.ts new file mode 100644 index 00000000..fa7b8ee4 --- /dev/null +++ b/packages/schema-generator/test/test-data/optional/toString.ts @@ -0,0 +1,15 @@ +import type { t } from '@traversable/schema' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType} | undefined)` +} + +export function toString(optionalSchema: t.optional): toString +export function toString({ def }: t.optional): () => string { + function optionalToString(): string { + return '(' + callToString(def) + ' | undefined)' + } + return optionalToString +} diff --git a/packages/schema-generator/test/test-data/optional/validate.ts b/packages/schema-generator/test/test-data/optional/validate.ts new file mode 100644 index 00000000..fe15bbc7 --- /dev/null +++ b/packages/schema-generator/test/test-data/optional/validate.ts @@ -0,0 +1,17 @@ +import { URI } from '@traversable/registry' +import { t } from '@traversable/schema' +import type { Validate, Validator, ValidationFn } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(optionalSchema: t.optional): validate +export function validate(optionalSchema: t.optional): validate +export function validate({ def }: t.optional): ValidationFn { + validateOptional.tag = URI.optional + validateOptional.optional = 1 + function validateOptional(u: unknown, path = Array.of()) { + if (u === void 0) return true + return def.validate(u, path) + } + return validateOptional +} diff --git a/packages/schema-generator/test/test-data/record/core.ts b/packages/schema-generator/test/test-data/record/core.ts new file mode 100644 index 00000000..d283b3ac --- /dev/null +++ b/packages/schema-generator/test/test-data/record/core.ts @@ -0,0 +1,41 @@ +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, Object_assign, safeCoerce, URI } from '@traversable/registry' +import { t, Predicate } from '@traversable/schema' + +export interface record extends record.core { + //<%= Types %> +} + +export function record(schema: S): record +export function record(schema: S): record> +export function record(schema: t.Schema) { + return record.def(schema) +} + +export namespace record { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): record + export function def(x: unknown): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const recordGuard = t.isPredicate(x) ? Predicate.is.record(safeCoerce(x)) : Predicate.is.anyObject + function RecordSchema(src: unknown) { return recordGuard(src) } + RecordSchema.tag = URI.record + RecordSchema.def = x + Object_assign(RecordSchema, record.userDefinitions) + return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) + } +} + +export declare namespace record { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.record + def: S + _type: Record + } + export type type> = never | T +} diff --git a/packages/schema-generator/test/test-data/record/equals.ts b/packages/schema-generator/test/test-data/record/equals.ts new file mode 100644 index 00000000..378f73c8 --- /dev/null +++ b/packages/schema-generator/test/test-data/record/equals.ts @@ -0,0 +1,32 @@ +import type { Equal } from '@traversable/registry' +import { Array_isArray, Object_is, Object_keys, Object_hasOwn } from '@traversable/registry' +import type { t } from '@traversable/schema' + +export type equals = never | Equal +export function equals(recordSchema: t.record): equals +export function equals(recordSchema: t.record): equals +export function equals({ def }: t.record<{ equals: Equal }>): Equal> { + function recordEquals(l: Record, r: Record): boolean { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + const lhs = Object_keys(l) + const rhs = Object_keys(r) + let len = lhs.length + let k: string + if (len !== rhs.length) return false + for (let ix = len; ix-- !== 0;) { + k = lhs[ix] + if (!Object_hasOwn(r, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + len = rhs.length + for (let ix = len; ix-- !== 0;) { + k = rhs[ix] + if (!Object_hasOwn(l, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + return true + } + return recordEquals +} diff --git a/packages/schema-generator/test/test-data/record/extension.ts b/packages/schema-generator/test/test-data/record/extension.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/test/test-data/record/extension.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/test/test-data/record/toJsonSchema.ts b/packages/schema-generator/test/test-data/record/toJsonSchema.ts new file mode 100644 index 00000000..7aa5e919 --- /dev/null +++ b/packages/schema-generator/test/test-data/record/toJsonSchema.ts @@ -0,0 +1,21 @@ +import type { t } from '@traversable/schema' +import type * as T from '@traversable/registry' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + type: 'object' + additionalProperties: T.Returns + } +} + +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { + return function recordToJsonSchema() { + return { + type: 'object' as const, + additionalProperties: getSchema(def), + } + } +} diff --git a/packages/schema-generator/test/test-data/record/toString.ts b/packages/schema-generator/test/test-data/record/toString.ts new file mode 100644 index 00000000..df26f100 --- /dev/null +++ b/packages/schema-generator/test/test-data/record/toString.ts @@ -0,0 +1,17 @@ +import type { Returns } from '@traversable/registry' +import type { t } from '@traversable/schema' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + /* @ts-expect-error */ + (): never | `Record}>` +} + +export function toString>(recordSchema: S): toString +export function toString(recordSchema: t.record): toString +export function toString({ def }: { def: unknown }): () => string { + function recordToString() { + return `Record` + } + return recordToString +} diff --git a/packages/schema-generator/test/test-data/record/validate.ts b/packages/schema-generator/test/test-data/record/validate.ts new file mode 100644 index 00000000..fc15bd60 --- /dev/null +++ b/packages/schema-generator/test/test-data/record/validate.ts @@ -0,0 +1,24 @@ +import type { t } from '@traversable/schema' +import { Array_isArray, Object_keys, URI } from '@traversable/registry' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = never | ValidationFn +export function validate(recordSchema: t.record): validate +export function validate(recordSchema: t.record): validate +export function validate({ def: { validate = () => true } }: t.record) { + validateRecord.tag = URI.record + function validateRecord(u: unknown, path = Array.of()) { + if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] + let errors = Array.of() + let keys = Object_keys(u) + for (let k of keys) { + let y = u[k] + let results = validate(y, [...path, k]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateRecord +} diff --git a/packages/schema-generator/test/test-data/string/core.ts b/packages/schema-generator/test/test-data/string/core.ts index eae438f6..4eca7733 100644 --- a/packages/schema-generator/test/test-data/string/core.ts +++ b/packages/schema-generator/test/test-data/string/core.ts @@ -1,25 +1,32 @@ -import type { Unknown } from '@traversable/registry' -import { Math_min, Math_max, Object_assign, URI } from '@traversable/registry' +import type { Integer, Unknown } from '@traversable/registry' +import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' import type { Bounds } from '@traversable/schema' import { __carryover as carryover, __within as within } from '@traversable/schema' interface string_ extends string_.core { - //<%= types %> + //<%= Types %> } -export let userDefinitions = { - //<%= terms %> +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> } export { string_ as string } -const string_ = Object_assign( - function StringSchema(src: unknown) { return typeof src === 'string' }, + +function StringSchema(src: unknown) { return typeof src === 'string' } +StringSchema.tag = URI.string +StringSchema.def = '' + +const string_ = Object_assign( + StringSchema, userDefinitions, ) as string_ -string_.tag = URI.string -string_.def = '' string_.min = function stringMinLength(minLength) { return Object_assign( boundedString({ gte: minLength }, carryover(this, 'minLength')), @@ -43,9 +50,14 @@ string_.between = function stringBetween( ) } +Object_assign( + string_, + bindUserExtensions(string_, userExtensions), +) + declare namespace string_ { interface core extends string_.methods { - (u: string | Unknown): u is string + (u: this['_type'] | Unknown): u is this['_type'] _type: string tag: URI.string def: this['_type'] @@ -53,9 +65,9 @@ declare namespace string_ { interface methods { minLength?: number maxLength?: number - min(minLength: Min): string_.Min - max(maxLength: Max): string_.Max - between(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> + min>(minLength: Min): string_.Min + max>(maxLength: Max): string_.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> } type Min = [Self] extends [{ maxLength: number }] diff --git a/packages/schema-generator/test/test-data/string/extension.ts b/packages/schema-generator/test/test-data/string/extension.ts index 8030c201..63b7046c 100644 --- a/packages/schema-generator/test/test-data/string/extension.ts +++ b/packages/schema-generator/test/test-data/string/extension.ts @@ -3,16 +3,19 @@ import { validate } from './validate.js' import { toString } from './toString.js' import { equals } from './equals.js' -export let extension = { - toString, - equals, - toJsonSchema, - validate, -} - -export interface Extension { +export interface Types { equals: equals toJsonSchema: toJsonSchema toString: toString validate: validate } + +export let Definitions = { + toString, + equals, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/test/test-data/string/validate.ts b/packages/schema-generator/test/test-data/string/validate.ts index f73175b0..92461ad5 100644 --- a/packages/schema-generator/test/test-data/string/validate.ts +++ b/packages/schema-generator/test/test-data/string/validate.ts @@ -6,7 +6,7 @@ import { NullaryErrors } from '@traversable/derive-validators' export type validate = ValidationFn export function validate(stringSchema: S): validate { validateString.tag = URI.string - function validateString(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + function validateString(u: unknown, path = Array.of()): true | ValidationError[] { return stringSchema(u) || [NullaryErrors.number(u, path)] } return validateString diff --git a/packages/schema-generator/test/test-data/tuple/core.ts b/packages/schema-generator/test/test-data/tuple/core.ts index 5e19d221..9a83de43 100644 --- a/packages/schema-generator/test/test-data/tuple/core.ts +++ b/packages/schema-generator/test/test-data/tuple/core.ts @@ -1,15 +1,16 @@ import type { SchemaOptions as Options, TypeError, + Unknown, } from '@traversable/registry' import { - bindUserDefinitions, + bindUserExtensions, getConfig, Object_assign, parseArgs, - replaceBooleanConstructor, - URI + safeCoerce, + URI, } from '@traversable/registry' import type { @@ -22,11 +23,8 @@ import { Predicate, } from '@traversable/schema' - -const replaceBoolean = replaceBooleanConstructor(t.nonnullable) - interface tuple extends tuple.core { - //<%= types %> + //<%= Types %> } export { tuple } @@ -36,33 +34,38 @@ function tuple(...args: [...schemas: tuple.valida function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> function tuple(...schemas: tuple.validate): tuple, S>> function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...args: | [...S] | [...S, Options]) { return tuple.def(...parseArgs(getConfig().schema, args)) } +function tuple(...args: [...t.Predicate[]] | [...t.Predicate[], Options]) { + return tuple.def(...parseArgs(getConfig().schema, args)) +} + namespace tuple { - export let proto = {} as tuple + export let userDefinitions: Record = { + //<%= Definitions %> + } as tuple export type type> = never | T export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { - let userDefinitions: Record = { - //<%= terms %> + let userExtensions: Record = { + //<%= Extensions %> } const opt = opt_ || xs.findIndex(t.optional.is) const options = { ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(t.optional.is) } satisfies tuple.InternalOptions - const tupleGuard = xs.every(t.isPredicate) - ? Predicate.is.tuple(options)(xs.map(replaceBoolean)) - : Predicate.is.anyArray + const tupleGuard = !xs.every(t.isPredicate) + ? Predicate.is.anyArray + : Predicate.is.tuple(options)(xs.map(safeCoerce)) function TupleSchema(src: unknown) { return tupleGuard(src) } TupleSchema.tag = URI.tuple TupleSchema.def = xs TupleSchema.opt = opt - Object_assign(TupleSchema, tuple.proto) - return Object_assign(TupleSchema, bindUserDefinitions(TupleSchema, userDefinitions)) + Object_assign(TupleSchema, tuple.userDefinitions) + return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) } } declare namespace tuple { interface core { - (u: unknown): u is this['_type'] + (u: this['_type'] | Unknown): u is this['_type'] tag: URI.tuple def: S _type: TupleType diff --git a/packages/schema-generator/test/test-data/tuple/extension.ts b/packages/schema-generator/test/test-data/tuple/extension.ts index 5af1315c..ee871114 100644 --- a/packages/schema-generator/test/test-data/tuple/extension.ts +++ b/packages/schema-generator/test/test-data/tuple/extension.ts @@ -3,16 +3,18 @@ import { toJsonSchema } from './toJsonSchema.js' import { toString } from './toString.js' import { validate } from './validate.js' -export interface Extension { +export interface Types { equals: equals toJsonSchema: toJsonSchema toString: toString validate: validate } -export let extension = { +export let Definitions = {} + +export let Extensions = { + toString, equals, toJsonSchema, - toString, validate, } diff --git a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts index 9f9c44e7..f612905a 100644 --- a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts @@ -3,17 +3,6 @@ import { t } from '@traversable/schema' import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' import type { MinItems } from '@traversable/schema-to-json-schema' -// export interface toJsonSchema { -// (): [this] extends [infer S] ? { -// type: 'array', -// items: { [I in keyof S]: Returns } -// additionalItems: false -// minItems: MinItems -// maxItems: S['length' & keyof S] -// } -// : never -// } - export interface toJsonSchema { (): { type: 'array', @@ -24,7 +13,6 @@ export interface toJsonSchema { } } -export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema export function toJsonSchema({ def }: t.tuple): () => { type: 'array' diff --git a/packages/schema-generator/test/test-data/tuple/validate.ts b/packages/schema-generator/test/test-data/tuple/validate.ts index 68c59438..b06bef4a 100644 --- a/packages/schema-generator/test/test-data/tuple/validate.ts +++ b/packages/schema-generator/test/test-data/tuple/validate.ts @@ -8,7 +8,7 @@ export function validate(tupleSchema: t.tuple<[. export function validate(tupleSchema: t.tuple<[...S]>): validate export function validate(tupleSchema: t.tuple<[...S]>): Validate { validateTuple.tag = URI.tuple - function validateTuple(u: unknown, path: (keyof any)[] = []) { + function validateTuple(u: unknown, path = Array.of()) { let errors = Array.of() if (!Array_isArray(u)) return [Errors.array(u, path)] for (let i = 0; i < tupleSchema.def.length; i++) { diff --git a/packages/schema-generator/test/test-data/union/core.ts b/packages/schema-generator/test/test-data/union/core.ts index a6363ae4..80dcc70b 100644 --- a/packages/schema-generator/test/test-data/union/core.ts +++ b/packages/schema-generator/test/test-data/union/core.ts @@ -1,42 +1,40 @@ -import { - bindUserDefinitions, - Object_assign, - URI -} from '@traversable/registry' - -import { - t, - Predicate, -} from '@traversable/schema' +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, Object_assign, safeCoerce, URI } from '@traversable/registry' +import { t, Predicate } from '@traversable/schema' export interface union extends union.core { - //<%= types %> + //<%= Types %> } export function union(...schemas: S): union export function union }>(...schemas: S): union -export function union(...schemas: S): {} { return union.def(schemas) } +export function union(...schemas: unknown[]) { + return union.def(schemas) +} + export namespace union { - export let proto = {} as union + export let userDefinitions: Record = { + //<%= Definitions %> + } as Partial> export function def(xs: T): union - export function def(xs: T): {} { - let userDefinitions = { - //<%= terms %> + export function def(xs: unknown[]) { + let userExtensions: Record = { + //<%= Extensions %> } - const anyOf = xs.every(t.isPredicate) ? Predicate.is.union(xs) : Predicate.is.unknown - function UnionSchema(src: unknown) { return anyOf(src) } + const anyOf = xs.every(t.isPredicate) ? Predicate.is.union(xs.map(safeCoerce)) : Predicate.is.unknown + function UnionSchema(src: unknown): src is unknown { return anyOf(src) } UnionSchema.tag = URI.union UnionSchema.def = xs - Object_assign(UnionSchema, union.proto) - return Object_assign(UnionSchema, bindUserDefinitions(UnionSchema, userDefinitions)) + Object_assign(UnionSchema, union.userDefinitions) + return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) } } export declare namespace union { interface core { - (u: unknown): u is this['_type'] + (u: this['_type'] | Unknown): u is this['_type'] tag: URI.union def: S - _type: S[number & keyof S]['_type' & keyof S[number & keyof S]] + _type: union.type } type type = never | T } diff --git a/packages/schema-generator/test/test-data/union/extension.ts b/packages/schema-generator/test/test-data/union/extension.ts index 5af1315c..ee871114 100644 --- a/packages/schema-generator/test/test-data/union/extension.ts +++ b/packages/schema-generator/test/test-data/union/extension.ts @@ -3,16 +3,18 @@ import { toJsonSchema } from './toJsonSchema.js' import { toString } from './toString.js' import { validate } from './validate.js' -export interface Extension { +export interface Types { equals: equals toJsonSchema: toJsonSchema toString: toString validate: validate } -export let extension = { +export let Definitions = {} + +export let Extensions = { + toString, equals, toJsonSchema, - toString, validate, } diff --git a/packages/schema-generator/test/test-data/union/validate.ts b/packages/schema-generator/test/test-data/union/validate.ts index 2c398bab..31737e40 100644 --- a/packages/schema-generator/test/test-data/union/validate.ts +++ b/packages/schema-generator/test/test-data/union/validate.ts @@ -8,7 +8,7 @@ export function validate(unionSchema: t.union export function validate(unionSchema: t.union): validate export function validate({ def }: t.union) { validateUnion.tag = URI.union - function validateUnion(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; let errors = Array.of() for (let i = 0; i < def.length; i++) { diff --git a/packages/schema-to-json-schema/src/exports.ts b/packages/schema-to-json-schema/src/exports.ts index f0d66b56..d204e630 100644 --- a/packages/schema-to-json-schema/src/exports.ts +++ b/packages/schema-to-json-schema/src/exports.ts @@ -10,7 +10,7 @@ export { export { toJsonSchema, fromJsonSchema } from './recursive.js' export { VERSION } from './version.js' export type { RequiredKeys } from './properties.js' -export { getSchema, isRequired, property } from './properties.js' +export { hasSchema, getSchema, isRequired, property, wrapOptional } from './properties.js' export type { MinItems } from './items.js' export { minItems } from './items.js' export type * from './specification.js' diff --git a/packages/schema-to-json-schema/src/jsonSchema.ts b/packages/schema-to-json-schema/src/jsonSchema.ts index 9a497423..8cb7a35b 100644 --- a/packages/schema-to-json-schema/src/jsonSchema.ts +++ b/packages/schema-to-json-schema/src/jsonSchema.ts @@ -47,7 +47,7 @@ const isNumber = (u: unknown): u is number => typeof u === 'number' type Nullable = Force -const getNumericBounds = (u: unknown): Spec.NumericBounds => ({ +export const getNumericBounds = (u: unknown): Spec.NumericBounds => ({ ...has('minimum', t.number)(u) && { minimum: u.minimum }, ...has('maximum', t.number)(u) && { maximum: u.maximum }, ...has('exclusiveMinimum', t.number)(u) && { exclusiveMinimum: u.exclusiveMinimum }, diff --git a/packages/schema-to-string/src/exports.ts b/packages/schema-to-string/src/exports.ts index 938c2ac5..a42d015f 100644 --- a/packages/schema-to-string/src/exports.ts +++ b/packages/schema-to-string/src/exports.ts @@ -3,5 +3,6 @@ export { callToString, hasToString, isShowable, + stringify, } from './shared.js' export { VERSION } from './version.js' diff --git a/packages/schema-to-string/src/shared.ts b/packages/schema-to-string/src/shared.ts index 6ad684b0..a32c8895 100644 --- a/packages/schema-to-string/src/shared.ts +++ b/packages/schema-to-string/src/shared.ts @@ -1,8 +1,8 @@ -export const hasToString = (x: unknown): x is { toString(): string } => +export let hasToString = (x: unknown): x is { toString(): string } => !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' -export const isShowable = (u: unknown) => u == null +export let isShowable = (u: unknown) => u == null || typeof u === 'boolean' || typeof u === 'number' || typeof u === 'bigint' @@ -10,3 +10,7 @@ export const isShowable = (u: unknown) => u == null ; export function callToString(x: unknown): string { return hasToString(x) ? x.toString() : 'unknown' } + +export let stringify + : (u: unknown) => string + = (u) => typeof u === 'string' ? `'${u}'` : isShowable(u) ? globalThis.String(u) : 'string' diff --git a/packages/schema-to-string/src/toString.ts b/packages/schema-to-string/src/toString.ts index 5ca45f7f..326969d0 100644 --- a/packages/schema-to-string/src/toString.ts +++ b/packages/schema-to-string/src/toString.ts @@ -1,7 +1,11 @@ import type { Returns, Join, Showable, UnionToTuple } from '@traversable/registry' import { symbol } from '@traversable/registry' import { t } from '@traversable/schema' -import { isShowable, hasToString } from './shared.js' +import { + isShowable, + hasToString, + stringify, +} from './shared.js' export { neverToString as never, @@ -38,9 +42,6 @@ const isOptional = (u: unknown): u is { toString(): T } => !!u && typeof u == Symbol_optional in u && typeof u[Symbol_optional] === 'number' -/** @internal */ -const stringify = (u: unknown) => typeof u === 'string' ? `'${u}'` : isShowable(u) ? globalThis.String(u) : 'string' - export function toString(x: unknown): string { return hasToString(x) ? x.toString() : 'unknown' } export declare namespace toString { diff --git a/packages/schema-to-string/test/toString.test.ts b/packages/schema-to-string/test/toString.test.ts index 1b43ead8..f088f545 100644 --- a/packages/schema-to-string/test/toString.test.ts +++ b/packages/schema-to-string/test/toString.test.ts @@ -451,6 +451,7 @@ vi.it('〖⛳️〗› ❲t.object(...).toString❳', () => ( | `{ 'a': { 'e': { 'g': 'a.e.g', 'f': 'a.e.f' }, 'b': { 'd': 'a.b.d', 'c': 'a.b.c' } }, 'h': { 'i': { 'j': 'h.i.j', 'k': 'h.i.k' }, 'l': { 'm': 'h.l.m', 'n': 'h.l.n' } } }` | `{ 'a': { 'e': { 'g': 'a.e.g', 'f': 'a.e.f' }, 'b': { 'd': 'a.b.d', 'c': 'a.b.c' } }, 'h': { 'l': { 'm': 'h.l.m', 'n': 'h.l.n' }, 'i': { 'j': 'h.i.j', 'k': 'h.i.k' } } }` | `{ 'h': { 'i': { 'j': 'h.i.j', 'k': 'h.i.k' }, 'l': { 'n': 'h.l.n', 'm': 'h.l.m' } }, 'a': { 'b': { 'd': 'a.b.d', 'c': 'a.b.c' }, 'e': { 'g': 'a.e.g', 'f': 'a.e.f' } } }` + | `{ 'h': { 'l': { 'm': 'h.l.m', 'n': 'h.l.n' }, 'i': { 'j': 'h.i.j', 'k': 'h.i.k' } }, 'a': { 'b': { 'c': 'a.b.c', 'd': 'a.b.d' }, 'e': { 'f': 'a.e.f', 'g': 'a.e.g' } } }` | `{ 'h': { 'l': { 'm': 'h.l.m', 'n': 'h.l.n' }, 'i': { 'j': 'h.i.j', 'k': 'h.i.k' } }, 'a': { 'b': { 'c': 'a.b.c', 'd': 'a.b.d' }, 'e': { 'g': 'a.e.g', 'f': 'a.e.f' } } }` | `{ 'h': { 'l': { 'm': 'h.l.m', 'n': 'h.l.n' }, 'i': { 'j': 'h.i.j', 'k': 'h.i.k' } }, 'a': { 'e': { 'g': 'a.e.g', 'f': 'a.e.f' }, 'b': { 'c': 'a.b.c', 'd': 'a.b.d' } } }` | `{ 'h': { 'l': { 'n': 'h.l.n', 'm': 'h.l.m' }, 'i': { 'j': 'h.i.j', 'k': 'h.i.k' } }, 'a': { 'e': { 'f': 'a.e.f', 'g': 'a.e.g' }, 'b': { 'd': 'a.b.d', 'c': 'a.b.c' } } }` @@ -515,6 +516,7 @@ vi.it('〖⛳️〗› ❲t.object(...).toString❳', () => ( | `{ 'a': { 'e'?: ({ 'f': 'a.e.f', 'g'?: ('a.e.g' | undefined) } | undefined), 'b': { 'c'?: ('a.b.c' | undefined), 'd': 'a.b.d' } }, 'h'?: ({ 'l': { 'n': 'h.l.n', 'm'?: ('h.l.m' | undefined) }, 'i'?: ({ 'k'?: ('h.i.k' | undefined), 'j': 'h.i.j' } | undefined) } | undefined) }` | `{ 'a': { 'e'?: ({ 'g'?: ('a.e.g' | undefined), 'f': 'a.e.f' } | undefined), 'b': { 'd': 'a.b.d', 'c'?: ('a.b.c' | undefined) } }, 'h'?: ({ 'i'?: ({ 'j': 'h.i.j', 'k'?: ('h.i.k' | undefined) } | undefined), 'l': { 'm'?: ('h.l.m' | undefined), 'n': 'h.l.n' } } | undefined) }` | `{ 'h'?: ({ 'i'?: ({ 'j': 'h.i.j', 'k'?: ('h.i.k' | undefined) } | undefined), 'l': { 'n': 'h.l.n', 'm'?: ('h.l.m' | undefined) } } | undefined), 'a': { 'b': { 'd': 'a.b.d', 'c'?: ('a.b.c' | undefined) }, 'e'?: ({ 'g'?: ('a.e.g' | undefined), 'f': 'a.e.f' } | undefined) } }` + | `{ 'h'?: ({ 'l': { 'm'?: ('h.l.m' | undefined), 'n': 'h.l.n' }, 'i'?: ({ 'j': 'h.i.j', 'k'?: ('h.i.k' | undefined) } | undefined) } | undefined), 'a': { 'b': { 'c'?: ('a.b.c' | undefined), 'd': 'a.b.d' }, 'e'?: ({ 'f': 'a.e.f', 'g'?: ('a.e.g' | undefined) } | undefined) } }` | `{ 'h'?: ({ 'l': { 'm'?: ('h.l.m' | undefined), 'n': 'h.l.n' }, 'i'?: ({ 'j': 'h.i.j', 'k'?: ('h.i.k' | undefined) } | undefined) } | undefined), 'a': { 'b': { 'c'?: ('a.b.c' | undefined), 'd': 'a.b.d' }, 'e'?: ({ 'g'?: ('a.e.g' | undefined), 'f': 'a.e.f' } | undefined) } }` | `{ 'h'?: ({ 'l': { 'm'?: ('h.l.m' | undefined), 'n': 'h.l.n' }, 'i'?: ({ 'j': 'h.i.j', 'k'?: ('h.i.k' | undefined) } | undefined) } | undefined), 'a': { 'e'?: ({ 'g'?: ('a.e.g' | undefined), 'f': 'a.e.f' } | undefined), 'b': { 'c'?: ('a.b.c' | undefined), 'd': 'a.b.d' } } }` | `{ 'h'?: ({ 'l': { 'n': 'h.l.n', 'm'?: ('h.l.m' | undefined) }, 'i'?: ({ 'j': 'h.i.j', 'k'?: ('h.i.k' | undefined) } | undefined) } | undefined), 'a': { 'e'?: ({ 'f': 'a.e.f', 'g'?: ('a.e.g' | undefined) } | undefined), 'b': { 'd': 'a.b.d', 'c'?: ('a.b.c' | undefined) } } }` diff --git a/packages/schema/src/exports.ts b/packages/schema/src/exports.ts index 12edfbe4..b09b439d 100644 --- a/packages/schema/src/exports.ts +++ b/packages/schema/src/exports.ts @@ -82,8 +82,6 @@ export type { TupleType, } from './schema.js' export { - /** @internal */ - replaceBooleanConstructor as __replaceBooleanConstructor, /** @internal */ carryover as __carryover, } from './schema.js' diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index f4046cd7..8f4a5fb3 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -16,6 +16,7 @@ import { has, Number_isSafeInteger, parseArgs, + safeCoerce, symbol, URI, } from '@traversable/registry' @@ -48,12 +49,6 @@ const Math_min = globalThis.Math.min /** @internal */ const Math_max = globalThis.Math.max -/** @internal */ -export function replaceBooleanConstructor(fn: T): LowerBound -export function replaceBooleanConstructor(fn: T) { - return fn === globalThis.Boolean ? nonnullable : fn -} - /** @internal */ export function carryover(x: T, ...ignoreKeys: (keyof T)[]) { let keys = Object.keys(x).filter((k) => !ignoreKeys.includes(k as never) && x[k as keyof typeof x] != null) @@ -705,12 +700,12 @@ namespace tuple { export type type> = never | T export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple /* v8 ignore next 1 */ - export function def(xs: readonly [...T], $: Options = getConfig().schema, opt_?: number) { + export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number) { const opt = opt_ || xs.findIndex(optional.is) const options = { ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(optional.is) } satisfies tuple.InternalOptions - const tupleGuard = xs.every(isPredicate) ? guard.tuple(options)(fn.map(xs, replaceBooleanConstructor)) : guard.anyArray + const tupleGuard = xs.every(isPredicate) ? guard.tuple(options)(fn.map(xs, safeCoerce)) : guard.anyArray function TupleSchema(src: unknown) { return tupleGuard(src) } TupleSchema.def = xs TupleSchema.opt = opt @@ -762,7 +757,7 @@ namespace object_ { const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => optional.is(xs[k])) const req = keys.filter((k) => !optional.is(xs[k])) const objectGuard = guard.record(isPredicate)(xs) - ? guard.object(fn.map(xs, replaceBooleanConstructor), applyOptions($)) + ? guard.object(fn.map(xs, safeCoerce), applyOptions($)) : guard.anyObject function ObjectSchema(src: unknown) { return objectGuard(src) } ObjectSchema.def = xs diff --git a/packages/schema/test/schema.test.ts b/packages/schema/test/schema.test.ts index e370ed1f..13dd63a1 100644 --- a/packages/schema/test/schema.test.ts +++ b/packages/schema/test/schema.test.ts @@ -12,7 +12,6 @@ import { recurse, t, clone, - __replaceBooleanConstructor as replaceBooleanConstructor, __carryover as carryover, } from '@traversable/schema' import * as Seed from './seed.js' @@ -717,10 +716,10 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema#config❳', () => { vi.assert.isFalse(t.isUnary({})) }) - vi.it('〖⛳️〗› ❲~replaceBooleanConstructor❳', () => { - vi.assert.equal(replaceBooleanConstructor(globalThis.Boolean), t.nonnullable) - vi.assert.equal(replaceBooleanConstructor(t.string), t.string) - }) + // vi.it('〖⛳️〗› ❲~replaceBooleanConstructor❳', () => { + // vi.assert.equal(replaceBooleanConstructor(globalThis.Boolean), t.nonnullable) + // vi.assert.equal(replaceBooleanConstructor(t.string), t.string) + // }) vi.it('〖⛳️〗› ❲clone❳', () => { vi.assert.isFunction(clone(t.number)) From 040c553725c46bcba605e38b982d66eed2a02385 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 10 Apr 2025 14:19:21 -0500 Subject: [PATCH 20/45] feat(generator): adds the following generated schemas - any - boolean - never - null - symbol - undefined - unknown - void --- .../test/test-data/any/core.ts | 36 +++++++++++++++++++ .../test/test-data/any/equals.ts | 7 ++++ .../test/test-data/any/extension.ts | 21 +++++++++++ .../test/test-data/any/toJsonSchema.ts | 5 +++ .../test/test-data/any/toString.ts | 2 ++ .../test/test-data/any/validate.ts | 10 ++++++ .../test/test-data/boolean/core.ts | 36 +++++++++++++++++++ .../test/test-data/boolean/equals.ts | 7 ++++ .../test/test-data/boolean/extension.ts | 21 +++++++++++ .../test/test-data/boolean/toJsonSchema.ts | 5 +++ .../test/test-data/boolean/toString.ts | 2 ++ .../test/test-data/boolean/validate.ts | 12 +++++++ .../test/test-data/never/core.ts | 36 +++++++++++++++++++ .../test/test-data/never/equals.ts | 6 ++++ .../test/test-data/never/extension.ts | 21 +++++++++++ .../test/test-data/never/toJsonSchema.ts | 5 +++ .../test/test-data/never/toString.ts | 2 ++ .../test/test-data/never/validate.ts | 10 ++++++ .../test/test-data/null/core.ts | 36 +++++++++++++++++++ .../test/test-data/null/equals.ts | 7 ++++ .../test/test-data/null/extension.ts | 21 +++++++++++ .../test/test-data/null/toJsonSchema.ts | 5 +++ .../test/test-data/null/toString.ts | 2 ++ .../test/test-data/null/validate.ts | 13 +++++++ .../test/test-data/symbol/core.ts | 36 +++++++++++++++++++ .../test/test-data/symbol/equals.ts | 7 ++++ .../test/test-data/symbol/extension.ts | 21 +++++++++++ .../test/test-data/symbol/toJsonSchema.ts | 5 +++ .../test/test-data/symbol/toString.ts | 2 ++ .../test/test-data/symbol/validate.ts | 13 +++++++ .../test/test-data/undefined/core.ts | 36 +++++++++++++++++++ .../test/test-data/undefined/equals.ts | 7 ++++ .../test/test-data/undefined/extension.ts | 21 +++++++++++ .../test/test-data/undefined/toJsonSchema.ts | 5 +++ .../test/test-data/undefined/toString.ts | 2 ++ .../test/test-data/undefined/validate.ts | 13 +++++++ .../test/test-data/unknown/core.ts | 36 +++++++++++++++++++ .../test/test-data/unknown/equals.ts | 7 ++++ .../test/test-data/unknown/extension.ts | 21 +++++++++++ .../test/test-data/unknown/toJsonSchema.ts | 5 +++ .../test/test-data/unknown/toString.ts | 2 ++ .../test/test-data/unknown/validate.ts | 10 ++++++ .../test/test-data/void/core.ts | 36 +++++++++++++++++++ .../test/test-data/void/equals.ts | 7 ++++ .../test/test-data/void/extension.ts | 21 +++++++++++ .../test/test-data/void/toJsonSchema.ts | 7 ++++ .../test/test-data/void/toString.ts | 2 ++ .../test/test-data/void/validate.ts | 13 +++++++ packages/schema/src/schema.ts | 2 -- 49 files changed, 663 insertions(+), 2 deletions(-) create mode 100644 packages/schema-generator/test/test-data/any/core.ts create mode 100644 packages/schema-generator/test/test-data/any/equals.ts create mode 100644 packages/schema-generator/test/test-data/any/extension.ts create mode 100644 packages/schema-generator/test/test-data/any/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/any/toString.ts create mode 100644 packages/schema-generator/test/test-data/any/validate.ts create mode 100644 packages/schema-generator/test/test-data/boolean/core.ts create mode 100644 packages/schema-generator/test/test-data/boolean/equals.ts create mode 100644 packages/schema-generator/test/test-data/boolean/extension.ts create mode 100644 packages/schema-generator/test/test-data/boolean/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/boolean/toString.ts create mode 100644 packages/schema-generator/test/test-data/boolean/validate.ts create mode 100644 packages/schema-generator/test/test-data/never/core.ts create mode 100644 packages/schema-generator/test/test-data/never/equals.ts create mode 100644 packages/schema-generator/test/test-data/never/extension.ts create mode 100644 packages/schema-generator/test/test-data/never/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/never/toString.ts create mode 100644 packages/schema-generator/test/test-data/never/validate.ts create mode 100644 packages/schema-generator/test/test-data/null/core.ts create mode 100644 packages/schema-generator/test/test-data/null/equals.ts create mode 100644 packages/schema-generator/test/test-data/null/extension.ts create mode 100644 packages/schema-generator/test/test-data/null/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/null/toString.ts create mode 100644 packages/schema-generator/test/test-data/null/validate.ts create mode 100644 packages/schema-generator/test/test-data/symbol/core.ts create mode 100644 packages/schema-generator/test/test-data/symbol/equals.ts create mode 100644 packages/schema-generator/test/test-data/symbol/extension.ts create mode 100644 packages/schema-generator/test/test-data/symbol/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/symbol/toString.ts create mode 100644 packages/schema-generator/test/test-data/symbol/validate.ts create mode 100644 packages/schema-generator/test/test-data/undefined/core.ts create mode 100644 packages/schema-generator/test/test-data/undefined/equals.ts create mode 100644 packages/schema-generator/test/test-data/undefined/extension.ts create mode 100644 packages/schema-generator/test/test-data/undefined/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/undefined/toString.ts create mode 100644 packages/schema-generator/test/test-data/undefined/validate.ts create mode 100644 packages/schema-generator/test/test-data/unknown/core.ts create mode 100644 packages/schema-generator/test/test-data/unknown/equals.ts create mode 100644 packages/schema-generator/test/test-data/unknown/extension.ts create mode 100644 packages/schema-generator/test/test-data/unknown/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/unknown/toString.ts create mode 100644 packages/schema-generator/test/test-data/unknown/validate.ts create mode 100644 packages/schema-generator/test/test-data/void/core.ts create mode 100644 packages/schema-generator/test/test-data/void/equals.ts create mode 100644 packages/schema-generator/test/test-data/void/extension.ts create mode 100644 packages/schema-generator/test/test-data/void/toJsonSchema.ts create mode 100644 packages/schema-generator/test/test-data/void/toString.ts create mode 100644 packages/schema-generator/test/test-data/void/validate.ts diff --git a/packages/schema-generator/test/test-data/any/core.ts b/packages/schema-generator/test/test-data/any/core.ts new file mode 100644 index 00000000..8e9de433 --- /dev/null +++ b/packages/schema-generator/test/test-data/any/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { any_ as any } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface any_ extends any_.core { + //<%= Types %> +} + +function AnySchema(src: unknown): src is any { return src === void 0 } +AnySchema.tag = URI.any +AnySchema.def = void 0 as any + +const any_ = Object_assign( + AnySchema, + userDefinitions, +) as any_ + +Object_assign(any_, userExtensions) + +declare namespace any_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.any + _type: any + def: this['_type'] + } +} diff --git a/packages/schema-generator/test/test-data/any/equals.ts b/packages/schema-generator/test/test-data/any/equals.ts new file mode 100644 index 00000000..f94e5ab5 --- /dev/null +++ b/packages/schema-generator/test/test-data/any/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: unknown, right: unknown): boolean { + return Object_is(left, right) +} \ No newline at end of file diff --git a/packages/schema-generator/test/test-data/any/extension.ts b/packages/schema-generator/test/test-data/any/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/test/test-data/any/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/test/test-data/any/toJsonSchema.ts b/packages/schema-generator/test/test-data/any/toJsonSchema.ts new file mode 100644 index 00000000..25336fc6 --- /dev/null +++ b/packages/schema-generator/test/test-data/any/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return unknownToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/any/toString.ts b/packages/schema-generator/test/test-data/any/toString.ts new file mode 100644 index 00000000..417e1048 --- /dev/null +++ b/packages/schema-generator/test/test-data/any/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } diff --git a/packages/schema-generator/test/test-data/any/validate.ts b/packages/schema-generator/test/test-data/any/validate.ts new file mode 100644 index 00000000..0bb8a4c4 --- /dev/null +++ b/packages/schema-generator/test/test-data/any/validate.ts @@ -0,0 +1,10 @@ +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.unknown): validate { + validateUnknown.tag = URI.unknown + function validateUnknown() { return true as const } + return validateUnknown +} diff --git a/packages/schema-generator/test/test-data/boolean/core.ts b/packages/schema-generator/test/test-data/boolean/core.ts new file mode 100644 index 00000000..3e27edf7 --- /dev/null +++ b/packages/schema-generator/test/test-data/boolean/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { boolean_ as boolean } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface boolean_ extends boolean_.core { + //<%= Types %> +} +function BooleanSchema(src: unknown): src is boolean { return src === void 0 } + +BooleanSchema.tag = URI.boolean +BooleanSchema.def = false + +const boolean_ = Object_assign( + BooleanSchema, + userDefinitions, +) as boolean_ + +Object_assign(boolean_, userExtensions) + +declare namespace boolean_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.boolean + _type: boolean + def: this['_type'] + } +} diff --git a/packages/schema-generator/test/test-data/boolean/equals.ts b/packages/schema-generator/test/test-data/boolean/equals.ts new file mode 100644 index 00000000..b32f3f3e --- /dev/null +++ b/packages/schema-generator/test/test-data/boolean/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: boolean, right: boolean): boolean { + return Object_is(left, right) +} \ No newline at end of file diff --git a/packages/schema-generator/test/test-data/boolean/extension.ts b/packages/schema-generator/test/test-data/boolean/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/test/test-data/boolean/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/test/test-data/boolean/toJsonSchema.ts b/packages/schema-generator/test/test-data/boolean/toJsonSchema.ts new file mode 100644 index 00000000..d1b86f70 --- /dev/null +++ b/packages/schema-generator/test/test-data/boolean/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'boolean' } } +export function toJsonSchema(): toJsonSchema { + function booleanToJsonSchema() { return { type: 'boolean' as const } } + return booleanToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/boolean/toString.ts b/packages/schema-generator/test/test-data/boolean/toString.ts new file mode 100644 index 00000000..3c408e57 --- /dev/null +++ b/packages/schema-generator/test/test-data/boolean/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'boolean' } +export function toString(): 'boolean' { return 'boolean' } diff --git a/packages/schema-generator/test/test-data/boolean/validate.ts b/packages/schema-generator/test/test-data/boolean/validate.ts new file mode 100644 index 00000000..97736de4 --- /dev/null +++ b/packages/schema-generator/test/test-data/boolean/validate.ts @@ -0,0 +1,12 @@ +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(booleanSchema: t.boolean): validate { + validateBoolean.tag = URI.boolean + function validateBoolean(u: unknown, path = Array.of()) { + return booleanSchema(true as const) || [NullaryErrors.null(u, path)] + } + return validateBoolean +} diff --git a/packages/schema-generator/test/test-data/never/core.ts b/packages/schema-generator/test/test-data/never/core.ts new file mode 100644 index 00000000..1f444290 --- /dev/null +++ b/packages/schema-generator/test/test-data/never/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { never_ as never } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface never_ extends never_.core { + //<%= Types %> +} + +function NeverSchema(src: unknown): src is never { return src === void 0 } +NeverSchema.tag = URI.never +NeverSchema.def = void 0 as never + +const never_ = Object_assign( + NeverSchema, + userDefinitions, +) as never_ + +Object_assign(never_, userExtensions) + +declare namespace never_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.never + _type: never + def: this['_type'] + } +} diff --git a/packages/schema-generator/test/test-data/never/equals.ts b/packages/schema-generator/test/test-data/never/equals.ts new file mode 100644 index 00000000..5377668e --- /dev/null +++ b/packages/schema-generator/test/test-data/never/equals.ts @@ -0,0 +1,6 @@ +import type { Equal } from "@traversable/registry" + +export type equals = Equal +export function equals(left: never, right: never): boolean { + return false +} \ No newline at end of file diff --git a/packages/schema-generator/test/test-data/never/extension.ts b/packages/schema-generator/test/test-data/never/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/test/test-data/never/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/test/test-data/never/toJsonSchema.ts b/packages/schema-generator/test/test-data/never/toJsonSchema.ts new file mode 100644 index 00000000..d22338df --- /dev/null +++ b/packages/schema-generator/test/test-data/never/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): never } +export function toJsonSchema(): toJsonSchema { + function neverToJsonSchema() { return void 0 as never } + return neverToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/never/toString.ts b/packages/schema-generator/test/test-data/never/toString.ts new file mode 100644 index 00000000..aaabf80d --- /dev/null +++ b/packages/schema-generator/test/test-data/never/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'never' } +export function toString(): 'never' { return 'never' } diff --git a/packages/schema-generator/test/test-data/never/validate.ts b/packages/schema-generator/test/test-data/never/validate.ts new file mode 100644 index 00000000..f316c281 --- /dev/null +++ b/packages/schema-generator/test/test-data/never/validate.ts @@ -0,0 +1,10 @@ +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.never): validate { + validateNever.tag = URI.never + function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } + return validateNever +} diff --git a/packages/schema-generator/test/test-data/null/core.ts b/packages/schema-generator/test/test-data/null/core.ts new file mode 100644 index 00000000..f263c284 --- /dev/null +++ b/packages/schema-generator/test/test-data/null/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { null_ as null, null_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface null_ extends null_.core { + //<%= Types %> +} + +function NullSchema(src: unknown): src is null { return src === void 0 } +NullSchema.def = null +NullSchema.tag = URI.null + +const null_ = Object_assign( + NullSchema, + userDefinitions, +) as null_ + +Object_assign(null_, userExtensions) + +declare namespace null_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.null + _type: null + def: this['_type'] + } +} diff --git a/packages/schema-generator/test/test-data/null/equals.ts b/packages/schema-generator/test/test-data/null/equals.ts new file mode 100644 index 00000000..963c47f5 --- /dev/null +++ b/packages/schema-generator/test/test-data/null/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: null, right: null): boolean { + return Object_is(left, right) +} \ No newline at end of file diff --git a/packages/schema-generator/test/test-data/null/extension.ts b/packages/schema-generator/test/test-data/null/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/test/test-data/null/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/test/test-data/null/toJsonSchema.ts b/packages/schema-generator/test/test-data/null/toJsonSchema.ts new file mode 100644 index 00000000..7a3b7c3a --- /dev/null +++ b/packages/schema-generator/test/test-data/null/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'null', enum: [null] } } +export function toJsonSchema(): toJsonSchema { + function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } + return nullToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/null/toString.ts b/packages/schema-generator/test/test-data/null/toString.ts new file mode 100644 index 00000000..35c3aef8 --- /dev/null +++ b/packages/schema-generator/test/test-data/null/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'null' } +export function toString(): 'null' { return 'null' } diff --git a/packages/schema-generator/test/test-data/null/validate.ts b/packages/schema-generator/test/test-data/null/validate.ts new file mode 100644 index 00000000..e0336913 --- /dev/null +++ b/packages/schema-generator/test/test-data/null/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(nullSchema: t.null): validate { + validateNull.tag = URI.null + function validateNull(u: unknown, path = Array.of()) { + return nullSchema(u) || [NullaryErrors.null(u, path)] + } + return validateNull +} diff --git a/packages/schema-generator/test/test-data/symbol/core.ts b/packages/schema-generator/test/test-data/symbol/core.ts new file mode 100644 index 00000000..366e80e0 --- /dev/null +++ b/packages/schema-generator/test/test-data/symbol/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { symbol_ as symbol } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface symbol_ extends symbol_.core { + //<%= Types %> +} + +function SymbolSchema(src: unknown): src is symbol { return src === void 0 } +SymbolSchema.tag = URI.symbol +SymbolSchema.def = Symbol() + +const symbol_ = Object_assign( + SymbolSchema, + userDefinitions, +) as symbol_ + +Object_assign(symbol_, userExtensions) + +declare namespace symbol_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.symbol + _type: symbol + def: this['_type'] + } +} diff --git a/packages/schema-generator/test/test-data/symbol/equals.ts b/packages/schema-generator/test/test-data/symbol/equals.ts new file mode 100644 index 00000000..4793732f --- /dev/null +++ b/packages/schema-generator/test/test-data/symbol/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: symbol, right: symbol): boolean { + return Object_is(left, right) +} \ No newline at end of file diff --git a/packages/schema-generator/test/test-data/symbol/extension.ts b/packages/schema-generator/test/test-data/symbol/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/test/test-data/symbol/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/test/test-data/symbol/toJsonSchema.ts b/packages/schema-generator/test/test-data/symbol/toJsonSchema.ts new file mode 100644 index 00000000..7046b08e --- /dev/null +++ b/packages/schema-generator/test/test-data/symbol/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function symbolToJsonSchema() { return void 0 } + return symbolToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/symbol/toString.ts b/packages/schema-generator/test/test-data/symbol/toString.ts new file mode 100644 index 00000000..5651fe27 --- /dev/null +++ b/packages/schema-generator/test/test-data/symbol/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'symbol' } +export function toString(): 'symbol' { return 'symbol' } diff --git a/packages/schema-generator/test/test-data/symbol/validate.ts b/packages/schema-generator/test/test-data/symbol/validate.ts new file mode 100644 index 00000000..eb3a9819 --- /dev/null +++ b/packages/schema-generator/test/test-data/symbol/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(symbolSchema: t.symbol): validate { + validateSymbol.tag = URI.symbol + function validateSymbol(u: unknown, path = Array.of()) { + return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] + } + return validateSymbol +} diff --git a/packages/schema-generator/test/test-data/undefined/core.ts b/packages/schema-generator/test/test-data/undefined/core.ts new file mode 100644 index 00000000..fb8b60bd --- /dev/null +++ b/packages/schema-generator/test/test-data/undefined/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { undefined_ as undefined } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface undefined_ extends undefined_.core { + //<%= Types %> +} + +function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } +UndefinedSchema.tag = URI.undefined +UndefinedSchema.def = void 0 as undefined + +const undefined_ = Object_assign( + UndefinedSchema, + userDefinitions, +) as undefined_ + +Object_assign(undefined_, userExtensions) + +declare namespace undefined_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.undefined + _type: undefined + def: this['_type'] + } +} diff --git a/packages/schema-generator/test/test-data/undefined/equals.ts b/packages/schema-generator/test/test-data/undefined/equals.ts new file mode 100644 index 00000000..2836cd51 --- /dev/null +++ b/packages/schema-generator/test/test-data/undefined/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: undefined, right: undefined): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/test/test-data/undefined/extension.ts b/packages/schema-generator/test/test-data/undefined/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/test/test-data/undefined/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/test/test-data/undefined/toJsonSchema.ts b/packages/schema-generator/test/test-data/undefined/toJsonSchema.ts new file mode 100644 index 00000000..be46c306 --- /dev/null +++ b/packages/schema-generator/test/test-data/undefined/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function undefinedToJsonSchema(): void { return void 0 } + return undefinedToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/undefined/toString.ts b/packages/schema-generator/test/test-data/undefined/toString.ts new file mode 100644 index 00000000..a48b744b --- /dev/null +++ b/packages/schema-generator/test/test-data/undefined/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'undefined' } +export function toString(): 'undefined' { return 'undefined' } diff --git a/packages/schema-generator/test/test-data/undefined/validate.ts b/packages/schema-generator/test/test-data/undefined/validate.ts new file mode 100644 index 00000000..318bfbad --- /dev/null +++ b/packages/schema-generator/test/test-data/undefined/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(undefinedSchema: t.undefined): validate { + validateUndefined.tag = URI.undefined + function validateUndefined(u: unknown, path = Array.of()) { + return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] + } + return validateUndefined +} diff --git a/packages/schema-generator/test/test-data/unknown/core.ts b/packages/schema-generator/test/test-data/unknown/core.ts new file mode 100644 index 00000000..7c70ea24 --- /dev/null +++ b/packages/schema-generator/test/test-data/unknown/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { unknown_ as unknown } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface unknown_ extends unknown_.core { + //<%= Types %> +} + +function UnknownSchema(src: unknown): src is unknown { return src === void 0 } +UnknownSchema.tag = URI.unknown +UnknownSchema.def = void 0 as unknown + +const unknown_ = Object_assign( + UnknownSchema, + userDefinitions, +) as unknown_ + +Object_assign(unknown_, userExtensions) + +declare namespace unknown_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.unknown + _type: unknown + def: this['_type'] + } +} diff --git a/packages/schema-generator/test/test-data/unknown/equals.ts b/packages/schema-generator/test/test-data/unknown/equals.ts new file mode 100644 index 00000000..385f31f4 --- /dev/null +++ b/packages/schema-generator/test/test-data/unknown/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: any, right: any): boolean { + return Object_is(left, right) +} \ No newline at end of file diff --git a/packages/schema-generator/test/test-data/unknown/extension.ts b/packages/schema-generator/test/test-data/unknown/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/test/test-data/unknown/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/test/test-data/unknown/toJsonSchema.ts b/packages/schema-generator/test/test-data/unknown/toJsonSchema.ts new file mode 100644 index 00000000..8d5be5a0 --- /dev/null +++ b/packages/schema-generator/test/test-data/unknown/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return anyToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/unknown/toString.ts b/packages/schema-generator/test/test-data/unknown/toString.ts new file mode 100644 index 00000000..f70aa050 --- /dev/null +++ b/packages/schema-generator/test/test-data/unknown/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'any' } +export function toString(): 'any' { return 'any' } diff --git a/packages/schema-generator/test/test-data/unknown/validate.ts b/packages/schema-generator/test/test-data/unknown/validate.ts new file mode 100644 index 00000000..7f1a9860 --- /dev/null +++ b/packages/schema-generator/test/test-data/unknown/validate.ts @@ -0,0 +1,10 @@ +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.any): validate { + validateAny.tag = URI.any + function validateAny() { return true as const } + return validateAny +} diff --git a/packages/schema-generator/test/test-data/void/core.ts b/packages/schema-generator/test/test-data/void/core.ts new file mode 100644 index 00000000..11fce4ab --- /dev/null +++ b/packages/schema-generator/test/test-data/void/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { void_ as void, void_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface void_ extends void_.core { + //<%= Types %> +} + +function VoidSchema(src: unknown): src is void { return src === void 0 } +VoidSchema.tag = URI.void +VoidSchema.def = void 0 as void + +const void_ = Object_assign( + VoidSchema, + userDefinitions, +) as void_ + +Object_assign(void_, userExtensions) + +declare namespace void_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.void + _type: void + def: this['_type'] + } +} diff --git a/packages/schema-generator/test/test-data/void/equals.ts b/packages/schema-generator/test/test-data/void/equals.ts new file mode 100644 index 00000000..6f7b9779 --- /dev/null +++ b/packages/schema-generator/test/test-data/void/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: void, right: void): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/test/test-data/void/extension.ts b/packages/schema-generator/test/test-data/void/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/test/test-data/void/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/test/test-data/void/toJsonSchema.ts b/packages/schema-generator/test/test-data/void/toJsonSchema.ts new file mode 100644 index 00000000..d636b569 --- /dev/null +++ b/packages/schema-generator/test/test-data/void/toJsonSchema.ts @@ -0,0 +1,7 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function voidToJsonSchema(): void { + return void 0 + } + return voidToJsonSchema +} diff --git a/packages/schema-generator/test/test-data/void/toString.ts b/packages/schema-generator/test/test-data/void/toString.ts new file mode 100644 index 00000000..487d08b3 --- /dev/null +++ b/packages/schema-generator/test/test-data/void/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'void' } +export function toString(): 'void' { return 'void' } diff --git a/packages/schema-generator/test/test-data/void/validate.ts b/packages/schema-generator/test/test-data/void/validate.ts new file mode 100644 index 00000000..6dbc61c2 --- /dev/null +++ b/packages/schema-generator/test/test-data/void/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(voidSchema: t.void): validate { + validateVoid.tag = URI.void + function validateVoid(u: unknown, path = Array.of()) { + return voidSchema(u) || [NullaryErrors.void(u, path)] + } + return validateVoid +} diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index 8f4a5fb3..7da93591 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -882,5 +882,3 @@ function boundedArray(schema: S, bounds: Bounds, carry?: {}): return Array_isArray(u) && within(bounds)(u.length) }, carry, array(schema)) } - -array(number_)._type From a9227c50cee05b830073fa8ee5adb6df00349394 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 10 Apr 2025 14:42:49 -0500 Subject: [PATCH 21/45] test(generator): tests that all the schemas build --- .../schema-generator/test/imports.test.ts | 74 +++++++++++++++++++ .../test/test-data/object/validate.ts | 4 - 2 files changed, 74 insertions(+), 4 deletions(-) diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index 306c2fe0..6821587d 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -36,6 +36,72 @@ let TODOs = void 0 let PATH = { __generated__: path.join(DIR_PATH, '__generated__'), sources: { + never: { + core: path.join(DATA_PATH, 'never', 'core.ts'), + extension: path.join(DATA_PATH, 'never', 'extension.ts'), + equals: path.join(DATA_PATH, 'never', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'never', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'never', 'toString.ts'), + validate: path.join(DATA_PATH, 'never', 'validate.ts'), + }, + unknown: { + core: path.join(DATA_PATH, 'unknown', 'core.ts'), + extension: path.join(DATA_PATH, 'unknown', 'extension.ts'), + equals: path.join(DATA_PATH, 'unknown', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'unknown', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'unknown', 'toString.ts'), + validate: path.join(DATA_PATH, 'unknown', 'validate.ts'), + }, + any: { + core: path.join(DATA_PATH, 'any', 'core.ts'), + extension: path.join(DATA_PATH, 'any', 'extension.ts'), + equals: path.join(DATA_PATH, 'any', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'any', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'any', 'toString.ts'), + validate: path.join(DATA_PATH, 'any', 'validate.ts'), + }, + void: { + core: path.join(DATA_PATH, 'void', 'core.ts'), + extension: path.join(DATA_PATH, 'void', 'extension.ts'), + equals: path.join(DATA_PATH, 'void', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'void', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'void', 'toString.ts'), + validate: path.join(DATA_PATH, 'void', 'validate.ts'), + }, + + null: { + core: path.join(DATA_PATH, 'null', 'core.ts'), + extension: path.join(DATA_PATH, 'null', 'extension.ts'), + equals: path.join(DATA_PATH, 'null', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'null', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'null', 'toString.ts'), + validate: path.join(DATA_PATH, 'null', 'validate.ts'), + }, + undefined: { + core: path.join(DATA_PATH, 'undefined', 'core.ts'), + extension: path.join(DATA_PATH, 'undefined', 'extension.ts'), + equals: path.join(DATA_PATH, 'undefined', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'undefined', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'undefined', 'toString.ts'), + validate: path.join(DATA_PATH, 'undefined', 'validate.ts'), + }, + + boolean: { + core: path.join(DATA_PATH, 'boolean', 'core.ts'), + extension: path.join(DATA_PATH, 'boolean', 'extension.ts'), + equals: path.join(DATA_PATH, 'boolean', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'boolean', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'boolean', 'toString.ts'), + validate: path.join(DATA_PATH, 'boolean', 'validate.ts'), + }, + symbol: { + core: path.join(DATA_PATH, 'symbol', 'core.ts'), + extension: path.join(DATA_PATH, 'symbol', 'extension.ts'), + equals: path.join(DATA_PATH, 'symbol', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'symbol', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'symbol', 'toString.ts'), + validate: path.join(DATA_PATH, 'symbol', 'validate.ts'), + }, integer: { core: path.join(DATA_PATH, 'integer', 'core.ts'), extension: path.join(DATA_PATH, 'integer', 'extension.ts'), @@ -134,6 +200,14 @@ let PATH = { }, }, targets: { + never: path.join(DIR_PATH, '__generated__', 'never.gen.ts'), + unknown: path.join(DIR_PATH, '__generated__', 'unknown.gen.ts'), + any: path.join(DIR_PATH, '__generated__', 'any.gen.ts'), + void: path.join(DIR_PATH, '__generated__', 'void.gen.ts'), + null: path.join(DIR_PATH, '__generated__', 'null.gen.ts'), + undefined: path.join(DIR_PATH, '__generated__', 'undefined.gen.ts'), + boolean: path.join(DIR_PATH, '__generated__', 'boolean.gen.ts'), + symbol: path.join(DIR_PATH, '__generated__', 'symbol.gen.ts'), integer: path.join(DIR_PATH, '__generated__', 'integer.gen.ts'), bigint: path.join(DIR_PATH, '__generated__', 'bigint.gen.ts'), number: path.join(DIR_PATH, '__generated__', 'number.gen.ts'), diff --git a/packages/schema-generator/test/test-data/object/validate.ts b/packages/schema-generator/test/test-data/object/validate.ts index d66ea624..6674d09e 100644 --- a/packages/schema-generator/test/test-data/object/validate.ts +++ b/packages/schema-generator/test/test-data/object/validate.ts @@ -62,10 +62,6 @@ export function validate(objectSchema: t.object<{ [x: string]: Validator }>): va else if (Object_hasOwn(u, k)) { let results = objectSchema.def[k].validate(u[k], path) if (results === true) continue - - console.log('results', objectSchema.def[k].validate) - console.log('results', results) - errors.push(...results) continue } else { From 66735db3ee5e512d7eedf1691455d165d04e7644 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Fri, 11 Apr 2025 11:27:49 -0500 Subject: [PATCH 22/45] cli partially implemented --- examples/sandbox/src/sandbox.tsx | 10 +- package.json | 6 +- packages/registry/src/exports.ts | 2 + packages/registry/src/globalThis.ts | 18 +- packages/registry/src/has.ts | 7 +- packages/registry/src/join.ts | 6 + packages/registry/src/satisfies.ts | 12 + packages/registry/src/types.ts | 4 +- packages/schema-generator/package.json | 20 +- .../src/__generated__/__manifest__.ts | 16 +- packages/schema-generator/src/cli.ts | 440 ++++++++++++++++++ .../src/{defineExtension.ts => define.ts} | 0 packages/schema-generator/src/exports.ts | 6 +- packages/schema-generator/src/imports.ts | 9 - packages/schema-generator/src/parser.ts | 98 +--- packages/schema-generator/test/e2e.test.ts | 7 +- .../schema-generator/test/generate.test.ts | 226 +++++++++ .../schema-generator/test/imports.test.ts | 274 +---------- packages/schema/src/postinstall.ts | 0 pnpm-lock.yaml | 392 +++++++++++++++- vite.config.ts | 1 + 21 files changed, 1151 insertions(+), 403 deletions(-) create mode 100644 packages/registry/src/join.ts create mode 100755 packages/schema-generator/src/cli.ts rename packages/schema-generator/src/{defineExtension.ts => define.ts} (100%) create mode 100644 packages/schema-generator/test/generate.test.ts create mode 100644 packages/schema/src/postinstall.ts diff --git a/examples/sandbox/src/sandbox.tsx b/examples/sandbox/src/sandbox.tsx index cc90c307..a9c3f94b 100644 --- a/examples/sandbox/src/sandbox.tsx +++ b/examples/sandbox/src/sandbox.tsx @@ -1,15 +1,15 @@ import { t } from './lib' // to view the inferred type predicates demo, see './demo/inferredTypePredicates.ts' import './demo/inferredTypePredicates' -// to view the '.toString' demo, check your browser console + see './demo/toString.ts' +// to view the '.toString' demo, see './demo/toString.ts' + check your browser console import './demo/toString' -// to view the '.toJsonSchema' demo, check your browser console + see './demo/toJsonSchema.ts' +// to view the '.toJsonSchema' demo, see './demo/toJsonSchema.ts' + check your browser console import './demo/toJsonSchema' -// to view the '.validate' demo, check your browser console + see './demo/validate.ts' +// to view the '.validate' demo, see './demo/validate.ts' + check your browser console import './demo/validate' -// to view the '.pipe' and '.extend' demo, check your browser console + see './demo/codec.ts' +// to view the '.pipe' and '.extend' demo, see './demo/codec.ts' + check your browser console import './demo/codec' -// to see how to extend traversable schemas in userland, check your browser console + see './demo/userlandExtensions.ts' +// to see how to extend traversable schemas in userland, see './demo/userlandExtensions.ts' + check your browser console import './demo/userlandExtensions' // to see how traversable schemas support reflection + custom interpreters, run this example with 'pnpm dev' import { HardcodedSchemaExamples, RandomlyGeneratedSchemas } from './demo/advanced' diff --git a/package.json b/package.json index b165406a..0da4e712 100644 --- a/package.json +++ b/package.json @@ -22,14 +22,16 @@ "clean:build": "pnpm -r --stream --parallel run clean:build", "clean:deps": "pnpm -r --stream --parallel run clean:deps && rm -rf node_modules", "clean:generated": "pnpm -r --stream --parallel run clean:generated", + "cli": "./packages/schema-generator/src/cli.ts", "describe": "pnpm run \"/^describe:.*/\"", "describe:project": "./bin/describe.ts", "dev": "pnpm --filter \"@traversable/sandbox\" run \"dev\"", "reboot": "./bin/reboot.ts", - "test": "vitest run -- --skipTypes && tsc -b tsconfig.json", + "test": "vitest run -- --skipTypes --exclude packages/**/test/e2e* && tsc -b tsconfig.json", + "test_": "vitest run -- --skipTypes && tsc -b tsconfig.json", "test:cov": "pnpm vitest run --coverage", "test:e2e": "pnpm vitest run --coverage", - "test:watch": "pnpm vitest --coverage", + "test:watch": "pnpm vitest run -- --exclude packages/schema-generator/test/e2e.test.ts", "test:watch:no-cov": "pnpm vitest", "workspace:new": "./bin/workspace-create.ts", "workspace:rm": "./bin/workspace-cleanup.ts" diff --git a/packages/registry/src/exports.ts b/packages/registry/src/exports.ts index ce867a34..94d09bd3 100644 --- a/packages/registry/src/exports.ts +++ b/packages/registry/src/exports.ts @@ -10,6 +10,8 @@ import * as symbol_ from './symbol.js' type symbol_ = typeof symbol_[keyof typeof symbol_] export { symbol_ as symbol } +export { join } from './join.js' + import * as URI_ from './uri.js' type URI_ = typeof URI_[keyof typeof URI_] export { URI_ as URI } diff --git a/packages/registry/src/globalThis.ts b/packages/registry/src/globalThis.ts index 6a9516f3..4253af3d 100644 --- a/packages/registry/src/globalThis.ts +++ b/packages/registry/src/globalThis.ts @@ -9,8 +9,10 @@ import type { PlainObject, SymbolIndexed, ObjectAsTypeAlias, - MixedNonFinite -} from "@traversable/registry/satisfies" + MixedNonFinite, + Entry, +} from './satisfies.js' +import { Force, newtype } from './types.js' export const Array_isArray : (u: unknown) => u is T[] @@ -36,9 +38,19 @@ export const Object_hasOwn = (u, k): u is never => !!u && typeof u === 'object' && globalThis.Object.prototype.hasOwnProperty.call(u, k) export const Object_keys - : (x: T) => (K)[] + : (x: T) => (K)[] = globalThis.Object.keys +export type Object_fromEntries = never | Force< + & { [E in Entry.Optional as E[0]]+?: E[1] } + & { [E in Entry.Required as E[0]]-?: E[1] } +> + +export const Object_fromEntries: { + >(entries: T[]): Object_fromEntries + >(entries: readonly T[]): Object_fromEntries +} = globalThis.Object.fromEntries + export type Object_entries = never | (K extends K ? [k: K, v: T[K & keyof T]] : never)[] export const Object_entries: { >(x: T): MixedNonFiniteEntries diff --git a/packages/registry/src/has.ts b/packages/registry/src/has.ts index 14a52eb3..d7cd5f8c 100644 --- a/packages/registry/src/has.ts +++ b/packages/registry/src/has.ts @@ -1,3 +1,4 @@ +import type { Unknown } from './types.js' import * as symbol from './symbol.js' /** @internal */ @@ -46,7 +47,7 @@ export function parsePath(xs: readonly (keyof any)[] | readonly [...(keyof any)[ : [xs.slice(0, -1), xs[xs.length - 1]] } -export type has = has.loop +export type has = has.loop export declare namespace has { export type loop @@ -56,9 +57,9 @@ export declare namespace has { } /** - * ## {@link has `tree.has`} + * ## {@link has `has`} * - * The {@link has `tree.has`} utility accepts a path + * The {@link has `has`} utility accepts a path * into a tree and an optional type-guard, and returns * a predicate that returns true if its argument * 'has' the specified path. diff --git a/packages/registry/src/join.ts b/packages/registry/src/join.ts new file mode 100644 index 00000000..4cd9d94d --- /dev/null +++ b/packages/registry/src/join.ts @@ -0,0 +1,6 @@ +import type { Join } from '@traversable/registry/types' + +export function join(left: readonly [...L], right: readonly [...R]): [...L, ...R] +export function join(left: readonly [...L], right: readonly [...R]) { + return [...left, ...right] +} diff --git a/packages/registry/src/satisfies.ts b/packages/registry/src/satisfies.ts index 2f386cdb..ab2e230e 100644 --- a/packages/registry/src/satisfies.ts +++ b/packages/registry/src/satisfies.ts @@ -41,6 +41,18 @@ export type Mut : [T] extends [infer U extends Atom] ? U : { -readonly [ix in keyof T]: Mut } +export type Entry = Mut> = [M] extends [Entry.Any] ? M : never +export declare namespace Entry { + type Any = { 0: keyof any, 1?: unknown } + type Required = T extends { 0: keyof any, 1: unknown } ? T : never + type Optional = T extends { 0: keyof any, 1: unknown } ? never : T +} + +export type Entries = Mut> = [M] extends [Entries.Any] ? M : never +export declare namespace Entries { + type Any = readonly Entry.Any[] +} + export type Mutable = never | { -readonly [K in keyof T]: T[K] } export type NonUnion< diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index c7e50cc9..024b9805 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -8,8 +8,8 @@ export { Match } from './satisfies.js' // data types export type Primitive = null | undefined | symbol | boolean | bigint | number | string export type Showable = null | undefined | boolean | bigint | number | string -export type Entry = readonly [k: string, v: T] -export type Entries = readonly Entry[] +// export type Entry = readonly [k: string, v: T] +// export type Entries = readonly Entry[] export type Unknown = {} | null | undefined /* @ts-expect-error */ diff --git a/packages/schema-generator/package.json b/packages/schema-generator/package.json index fa6cecb4..cdfecabc 100644 --- a/packages/schema-generator/package.json +++ b/packages/schema-generator/package.json @@ -15,14 +15,23 @@ "email": "ahrjarrett@gmail.com" }, "@traversable": { - "generateExports": { "include": ["**/*.ts"] }, - "generateIndex": { "include": ["**/*.ts"] } + "generateExports": { + "include": [ + "**/*.ts" + ] + }, + "generateIndex": { + "include": [ + "**/*.ts" + ] + } }, "publishConfig": { "access": "public", "directory": "dist", "registry": "https://registry.npmjs.org" }, + "bin": "./src/cli.ts", "scripts": { "bench": "echo NOTHING TO BENCH", "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", @@ -33,6 +42,7 @@ "clean": "pnpm run \"/^clean:.*/\"", "clean:build": "rm -rf .tsbuildinfo dist build", "clean:deps": "rm -rf node_modules", + "postinstall": "./src/cli.ts", "test": "vitest" }, "peerDependencies": { @@ -40,10 +50,12 @@ "@traversable/schema": "workspace:^" }, "devDependencies": { + "@clack/prompts": "^0.10.1", "@traversable/derive-validators": "workspace:^", - "@traversable/schema-to-json-schema": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema": "workspace:^", - "@traversable/schema-to-string": "workspace:^" + "@traversable/schema-to-json-schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^", + "picocolors": "^1.1.1" } } diff --git a/packages/schema-generator/src/__generated__/__manifest__.ts b/packages/schema-generator/src/__generated__/__manifest__.ts index f26b6529..255d0549 100644 --- a/packages/schema-generator/src/__generated__/__manifest__.ts +++ b/packages/schema-generator/src/__generated__/__manifest__.ts @@ -15,14 +15,19 @@ export default { "email": "ahrjarrett@gmail.com" }, "@traversable": { - "generateExports": { "include": ["**/*.ts"] }, - "generateIndex": { "include": ["**/*.ts"] } + "generateExports": { + "include": ["**/*.ts"] + }, + "generateIndex": { + "include": ["**/*.ts"] + } }, "publishConfig": { "access": "public", "directory": "dist", "registry": "https://registry.npmjs.org" }, + "bin": "./src/cli.ts", "scripts": { "bench": "echo NOTHING TO BENCH", "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", @@ -33,6 +38,7 @@ export default { "clean": "pnpm run \"/^clean:.*/\"", "clean:build": "rm -rf .tsbuildinfo dist build", "clean:deps": "rm -rf node_modules", + "postinstall": "./src/cli.ts", "test": "vitest" }, "peerDependencies": { @@ -40,10 +46,12 @@ export default { "@traversable/schema": "workspace:^" }, "devDependencies": { + "@clack/prompts": "^0.10.1", "@traversable/derive-validators": "workspace:^", - "@traversable/schema-to-json-schema": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema": "workspace:^", - "@traversable/schema-to-string": "workspace:^" + "@traversable/schema-to-json-schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^", + "picocolors": "^1.1.1" } } as const \ No newline at end of file diff --git a/packages/schema-generator/src/cli.ts b/packages/schema-generator/src/cli.ts new file mode 100755 index 00000000..9f221f32 --- /dev/null +++ b/packages/schema-generator/src/cli.ts @@ -0,0 +1,440 @@ +#!/usr/bin/env pnpm dlx tsx +import * as fs from 'node:fs' +import * as path from 'node:path' +import { execSync as x } from 'node:child_process' + +import * as p from '@clack/prompts' +import color from 'picocolors' +import { VERSION } from './version.js' + +import { t } from '@traversable/schema' + +let Primitives = t.eq([ + { value: 'null', label: 'null' }, + { value: 'undefined', label: 'undefined' }, + { value: 'boolean', label: 'boolean' }, + { value: 'number', label: 'number' }, + { value: 'string', label: 'string' }, + { value: 'bigint', label: 'bigint' }, + { value: 'integer', label: 'integer' }, + { value: 'unknown', label: 'unknown' }, + { value: 'never', label: 'never' }, + { value: 'void', label: 'void' }, + { value: 'any', label: 'any' }, + { value: 'symbol', label: 'symbol' }, +]).def + +let Combinators = t.eq([ + { value: 'object', label: 'object' }, + { value: 'array', label: 'array' }, + { value: 'union', label: 'union' }, + { value: 'intersect', label: 'intersect' }, + { value: 'record', label: 'record' }, + { value: 'optional', label: 'optional' }, + { value: 'tuple', label: 'tuple' }, + { value: 'eq', label: 'eq' }, +]).def + +let Features = t.eq([ + { value: 'equals', label: 'equals' }, + { value: 'toJsonSchema', label: 'toJsonSchema' }, + { value: 'toString', label: 'toString' }, + { value: 'validate', label: 'validate' }, +]).def + +async function prompt() { + p.intro(' 🌳 ' + color.underline(VERSION) + ' ') + + let $ = await p.group({ + target: () => p.text({ + message: 'Where would you like to install your schemas?', + placeholder: 'Path to an empty directory', + validate(path) { + if (!path.startsWith('./')) + return 'Please enter a relative path (e.g., \'./schemas\'' + }, + }), + primitives: () => p.multiselect({ + message: 'Pick your primitives', + initialValues: ['*'], + options: [ + { value: '*', label: '* (all)' }, + ...Primitives, + ], + }), + combinators: () => p.multiselect({ + message: 'Pick your combinators', + initialValues: ['*'], + options: [ + { value: '*', label: '* (all)' }, + ...Combinators, + ] + }), + features: () => p.multiselect({ + message: 'Which methods methods would you like to install?', + options: Features, + }) + }, { + onCancel: () => { + p.cancel('Operation cancelled.') + process.exit(0) + }, + }) + + let TARGET = path.join(process.cwd(), $.target) + let PACKAGE_JSON = path.join(TARGET, 'package.json') + let NODE_MODULES = path.join(TARGET, 'node_modules') + let TRAVERSABLE_SCHEMA = path.join(NODE_MODULES, '@traversable/schema') + + + if (!fs.existsSync(TARGET)) { + throw Error('The path ' + TARGET + ' does not exist.') + } + if (!fs.statSync(TARGET).isDirectory()) { + console.log(TARGET + ' is not a directory.') + process.exit(1) + } + try { + fs.accessSync(TARGET, fs.constants.R_OK | fs.constants.W_OK) + } catch (e) { + console.log('\n' + + 'Unable to write to path ' + + TARGET + + '. It\'s likely you do not have permissions for this folder.') + } + if (!fs.statSync(NODE_MODULES).isDirectory()) { + console.log('' + + '\n' + + 'The path ' + + TARGET + + ' does not contain a node_modules folder.' + + '\n' + ) + process.exit(1) + } + if (!fs.statSync(TRAVERSABLE_SCHEMA).isDirectory()) { + let installCore = await p.confirm({ + message: 'This script will install \'@traversable/schema\' to ' + NODE_MODULES + '. Proceed?', + initialValue: true, + }) + if (installCore === false) { + console.log('Exiting...') + process.exit(0) + } + } + + return $ +} + +function postinstall() { + let USER_DIR = process.env.INIT_CWD + let LIB_DIR = process.cwd() + console.log('USER_DIR', USER_DIR) + console.log('LIB_DIR', LIB_DIR) + + if (!USER_DIR) { + throw Error('Unable to infer the initial working directory') + } + try { + const USER_PKG_JSON = JSON.parse(fs.readFileSync(path.resolve(USER_DIR, 'package.json'), 'utf8')) + const LIB_PKG_JSON = JSON.parse(fs.readFileSync(path.resolve(LIB_DIR, 'package.json'), 'utf8')) + console.log('\n\nuser package.json: \n', USER_PKG_JSON) + console.log('\n\nlib package.json: \n', LIB_PKG_JSON) + } catch (e) { + console.log(e) + throw Error('Could not read package.json') + } + + setTimeout(() => { + x('echo hey') + + // let answers = prompt() + // console.log('answers', answers) + }, 3000) +} + +postinstall() + + +// type SchemaByName = typeof SchemaByName +// let SchemaByName = { +// any: t.any, +// array: t.array, +// bigint: t.bigint, +// boolean: t.boolean, +// eq: t.eq, +// integer: t.integer, +// intersect: t.intersect, +// never: t.never, +// null: t.null, +// number: t.number, +// object: t.object, +// optional: t.optional, +// record: t.record, +// string: t.string, +// symbol: t.symbol, +// tuple: t.tuple, +// undefined: t.undefined, +// union: t.union, +// unknown: t.unknown, +// void: t.void, +// } satisfies Record + +// type SchemaName = t.typeof +// let SchemaName = t.enum(...t.typeNames) + +// type Options = t.typeof +// interface Config extends Required> { +// schemas: { [K in S]: SchemaByName[K] } +// } + +// let Options = t.object({ +// cwd: t.optional(t.string), +// schemas: t.optional(t.array(SchemaName)), +// }) + +// // let getTarget = await p.text({ +// // message: 'Where would you like to install your schemas?', +// // placeholder: 'Path to an empty directory', +// // validate(value) { +// // if (!t.filter(t.string, (s) => s.startsWith('./'))) +// // return 'Please enter a relative path (e.g., \'./schemas\'' +// // }, +// // }) + +// // let multiselectOptions = Object +// // .entries(SchemaByName) +// // .map(([schemaName]) => ({ value: schemaName } satisfies p.Option)) + +// // .map(([schemaName]) => ({ value: schemaName } satisfies Prompt.Option)); + +// // let multiselect = p.multiselect({ +// // message: 'Select your schemas', +// // options: multiselectOptions, +// // required: true, +// // initialValues: Object.keys(SchemaName), +// // }) + + +// // let confirm = await p.confirm({ +// // message: 'Ready?', +// // }) + +// // p.intro('create-my-app'); +// // p.outro('You're all set!'); + + +// // type PromptGroup = { +// // [P in keyof T]: (opts: { +// // results: Prettify>>>; +// // }) => undefined | Promise; +// // }; + +// let RelativePath = t.filter(t.string, (s) => s.startsWith('./')) + +// let spinner = p.spinner() + +// let Primitives = [ +// { value: 'null', label: 'null' }, +// { value: 'undefined', label: 'undefined' }, +// { value: 'boolean', label: 'boolean' }, +// { value: 'number', label: 'number' }, +// { value: 'string', label: 'string' }, +// { value: 'bigint', label: 'bigint' }, +// { value: 'integer', label: 'integer' }, +// { value: 'unknown', label: 'unknown' }, +// { value: 'never', label: 'never' }, +// { value: 'void', label: 'void' }, +// { value: 'any', label: 'any' }, +// { value: 'symbol', label: 'symbol' }, +// ] as const satisfies p.Option[] + +// let Combinators = [ +// { value: 'object', label: 'object' }, +// { value: 'array', label: 'array' }, +// { value: 'union', label: 'union' }, +// { value: 'intersect', label: 'intersect' }, +// { value: 'record', label: 'record' }, +// { value: 'optional', label: 'optional' }, +// { value: 'tuple', label: 'tuple' }, +// { value: 'eq', label: 'eq' }, +// ] as const satisfies p.Option[] + +// let Features = [ +// { value: 'equals', label: 'equals' }, +// { value: 'toJsonSchema', label: 'toJsonSchema' }, +// { value: 'toString', label: 'toString' }, +// { value: 'validate', label: 'validate' }, +// ] as const satisfies p.Option[] + +// async function main() { +// p.intro(' 🌳 ' + color.underline(VERSION) + ' ') + +// let $ = await p.group({ +// target: () => p.text({ +// message: 'Where would you like to install your schemas?', +// placeholder: 'Path to an empty directory', +// validate(path) { +// if (!path.startsWith('./')) +// return 'Please enter a relative path (e.g., \'./schemas\'' +// }, +// }), +// primitives: () => p.multiselect({ +// message: 'Pick your primitives', +// initialValues: ['*'], +// options: [ +// { value: '*', label: '* (all)' }, +// ...Primitives, +// ], +// }), +// combinators: () => p.multiselect({ +// message: 'Pick your combinators', +// initialValues: ['*'], +// options: [ +// { value: '*', label: '* (all)' }, +// ...Combinators, +// ] +// }), +// features: () => p.multiselect({ +// message: 'Which methods methods would you like to install?', +// options: Features, +// }) +// }, { +// onCancel: () => { +// p.cancel('Operation cancelled.') +// process.exit(0) +// }, +// }) + +// let TARGET = path.join(process.cwd(), $.target) +// let PACKAGE_JSON = path.join(TARGET, 'package.json') +// let NODE_MODULES = path.join(TARGET, 'node_modules') +// let TRAVERSABLE_SCHEMA = path.join(NODE_MODULES, '@traversable/schema') + + +// if (!fs.existsSync(TARGET)) { +// throw Error('The path ' + TARGET + ' does not exist.') +// } +// if (!fs.statSync(TARGET).isDirectory()) { +// console.log(TARGET + ' is not a directory.') +// process.exit(1) +// } +// try { +// fs.accessSync(TARGET, fs.constants.R_OK | fs.constants.W_OK) +// } catch (e) { +// console.log('\n' +// + 'Unable to write to path ' +// + TARGET +// + '. It\'s likely you do not have permissions for this folder.') +// } +// if (!fs.statSync(NODE_MODULES).isDirectory()) { +// console.log('' +// + '\n' +// + 'The path ' +// + TARGET +// + ' does not contain a node_modules folder.' +// + '\n' +// ) +// process.exit(1) +// } +// if (!fs.statSync(TRAVERSABLE_SCHEMA).isDirectory()) { +// let installCore = await p.confirm({ +// message: 'This script will install \'@traversable/schema\' to ' + NODE_MODULES + '. Proceed?', +// initialValue: true, +// }) +// if (installCore === false) { +// console.log('Exiting...') +// process.exit(0) +// } +// } + +// // console.log('target', TARGET) +// // console.log('NODE_MODULES', NODE_MODULES) +// console.log('@traversable/schema path:', TRAVERSABLE_SCHEMA) + +// // console.log('primitives', primitives) +// // console.log('combinators', combinators) +// } + +// await main() + + +// // let defaultOptions = { +// // cwd: process.cwd(), +// // schemas: SchemaByName, +// // } satisfies Config + +// // function configure(options?: Options): Config +// // function configure(options?: Options) { +// // if (!options) return defaultOptions +// // else if (Options(options)) { +// // if (options.schemas) { +// // let schemas: { [K in SchemaName]+?: SchemaByName[keyof SchemaByName] } = {} +// // for (let schemaName of options.schemas) { +// // schemas[schemaName] = SchemaByName[schemaName] +// // } +// // return { +// // cwd: options.cwd ?? defaultOptions.cwd, +// // schemas, +// // } satisfies Config +// // } +// // } +// // else return defaultOptions +// // } + +// // interface Error extends globalThis.Error { +// // tag: Tag +// // } + +// // function createError(tag: Tag, defineError: ($: Config) => string): ($: Config) => Error +// // function createError(tag: Tag, defineError: ($: Config) => string): ($: Config) => globalThis.Error { +// // return ($: Config) => { +// // let error: globalThis.Error & { tag?: string } = globalThis.Error(defineError($)) +// // error.tag = tag +// // return error +// // } +// // } + +// // let UnableToResolveCwd = createError( +// // 'UnableToResolveCwd', +// // ($) => 'The path ' + $.cwd + ' does not exist.' +// // ) + +// // let NoWriteAccessToCwd = createError( +// // 'NoWriteAccessToCwd', +// // ($) => 'Unable to write to path ' + $.cwd + '. It\'s likely you do not have permissions for this folder.' +// // ) + +// // let UnableToFindPackageJson = createError( +// // 'UnableToFindPackageJson', +// // ($) => 'Unable to locate a package.json file in ' + $.cwd + '.' +// // ) + +// // let MalformedPackageJson = createError( +// // 'UnableToFindPackageJson', +// // ($) => 'Unable to read package.json file at ' + $.cwd + '.' +// // ) + +// // function init(options?: Options) { +// // let $ = configure(options) +// // let ERRORS = Array.of>() +// // if (!fs.existsSync($.cwd)) ERRORS.push(UnableToResolveCwd($)) + +// // try { fs.accessSync($.cwd, fs.constants.R_OK | fs.constants.W_OK) } +// // catch (e) { ERRORS.push(NoWriteAccessToCwd($)) } + +// // let PACKAGE_JSON_PATH = path.resolve($.cwd, 'package.json') +// // if (!fs.existsSync(PACKAGE_JSON_PATH)) ERRORS.push(UnableToFindPackageJson($)) + +// // try { let PACKAGE_JSON = fs.readFileSync(PACKAGE_JSON_PATH) } +// // catch (e) { ERRORS.push(MalformedPackageJson($)) } + +// // if (ERRORS.length > 0) { +// // for (let error of ERRORS) { +// // console.error('\n[Error]: ' + error.tag + '\r\n\n' + error.message) +// // console.debug() +// // } +// // process.exit(1) +// // } + +// // } diff --git a/packages/schema-generator/src/defineExtension.ts b/packages/schema-generator/src/define.ts similarity index 100% rename from packages/schema-generator/src/defineExtension.ts rename to packages/schema-generator/src/define.ts diff --git a/packages/schema-generator/src/exports.ts b/packages/schema-generator/src/exports.ts index cc5ef447..9fb3e69e 100644 --- a/packages/schema-generator/src/exports.ts +++ b/packages/schema-generator/src/exports.ts @@ -1,8 +1,8 @@ export * from './version.js' export * as P from './parser-combinators.js' -export type { DefineExtension } from './defineExtension.js' -export { defineExtension } from './defineExtension.js' +export type { DefineExtension } from './define.js' +export { defineExtension } from './define.js' export type { ExtensionsBySchemaName, @@ -32,5 +32,3 @@ export { parseSourceFile, replaceExtensions, } from './parser.js' - - diff --git a/packages/schema-generator/src/imports.ts b/packages/schema-generator/src/imports.ts index 5d3efa2c..b03400f5 100644 --- a/packages/schema-generator/src/imports.ts +++ b/packages/schema-generator/src/imports.ts @@ -176,12 +176,3 @@ export function makeImports(extensionsBySchemaName: ExtensionsBySchemaName): {} (importArray) => importArray.join('\n'), ) } - -// let getDependenciesFromImportsBySchemaName = (extensionsBySchemaName: ExtensionsBySchemaName) => { -// let xs = Object.values(extensionsBySchemaName) -// .filter((_) => !!_) -// .flatMap((_) => Object.values(_).filter((_) => !!_)) -// .flatMap((_) => Object.keys(_).filter((_) => _.startsWith('@traversable/'))) - -// return Array.from(new Set(xs)) -// } diff --git a/packages/schema-generator/src/parser.ts b/packages/schema-generator/src/parser.ts index 04381b92..e440ef94 100644 --- a/packages/schema-generator/src/parser.ts +++ b/packages/schema-generator/src/parser.ts @@ -25,7 +25,6 @@ export type ParsedExtensionFile = never | { [VarName.Ext]?: string } - export let typesMarker = `//<%= ${VarName.Type} %>` as const export let definitionsMarker = `//<%= ${VarName.Def} %>` as const export let extensionsMarker = `//<%= ${VarName.Ext} %>` as const @@ -171,22 +170,6 @@ type Splice = { let spliceComparator: Comparator = (l, r) => l.start < r.start ? -1 : r.start < l.start ? 1 : 0 -function splice1(source: string, x: Splice) { - return '' - + source.slice(0, x.start) - + '\n' + ' '.repeat(x.firstLineOffset) + x.content.split('\n').map((_) => ' '.repeat(x.offset) + _).join().trimStart() - + source.slice(x.end) -} - -function splice2(source: string, first: Splice, second: Splice) { - return '' - + source.slice(0, first.start) - + '\n' + ' '.repeat(first.firstLineOffset) + first.content.split('\n').map((_) => ' '.repeat(first.offset) + _).join().trimStart() - + source.slice(first.end, second.start) - + '\n' + ' '.repeat(second.firstLineOffset) + second.content.split('\n').map((_) => ' '.repeat(second.offset) + _).join('\n').trimStart() - + source.slice(second.end) -} - function splice3(source: string, first: Splice, second: Splice, third: Splice) { return '' + source.slice(0, first.start) @@ -241,72 +224,21 @@ export function replaceExtensions(source: string, parsedExtensionFile: ParsedExt let splices = unsortedSplices.sort(spliceComparator) - // console.log('splices', splices) - - let out = splice3(source, ...splices) - console.log(out) - return out + return splice3(source, ...splices) } +function splice1(source: string, x: Splice) { + return '' + + source.slice(0, x.start) + + '\n' + ' '.repeat(x.firstLineOffset) + x.content.split('\n').map((_) => ' '.repeat(x.offset) + _).join().trimStart() + + source.slice(x.end) +} -// let typeOffset = Math.max(typeMarker.result.value[1].length - 1, 0) -// let typeOffsetFirstLine = Math.max(typeMarker.result.value[1].length - 1, 0) -// let defOffset = Math.max(defMarker.result.value[1].length - 3, 0) -// let defOffsetFirstLine = Math.max(defMarker.result.value[1].length - 1, 0) -// let extOffset = Math.max(defMarker.result.value[1].length - 3, 0) -// let extOffsetFirstLine = Math.max(extMarker.result.value[1].length - 1, 0) - -// let typeIndent = ' '.repeat(typeOffset) -// let defIndent = ' '.repeat(defOffset) -// let extIndent = ' '.repeat(extOffset) - -// let defIndentFirstLine = ' '.repeat(defOffsetFirstLine) -// let extIndentFirstLine = ' '.repeat(extOffsetFirstLine) - - - - -// /** -// * ORDER: -// * 1. types -// * 2. definitions -// * 3. extensions -// */ -// if (typeMarker.index < defMarker.index && defMarker.index < extMarker.index) { -// return '' -// + source.slice(0, typeMarker.index + 1) -// + extension.type -// + source.slice(typeMarker.result.index - 1, termMarker.index + 1) -// + '\n' + termIndentFirstLine + extension.term.split('\n').map((_) => termIndent + _).join('\n').trimStart() -// + source.slice(termMarker.result.index - 1) -// } - -// /** -// * ORDER: -// * 1. types -// * 2. extensions -// * 3. definitions -// */ - -// /** -// * ORDER: -// * 1. definitions -// * 2. types -// * 3. extensions -// */ - -// /** -// * ORDER: -// * 1. definitions -// * 2. extensions -// * 3. extensions -// */ - -// else { -// return '' -// + source.slice(0, termMarker.index + 1) -// + '\n' + termIndentFirstLine + extension.term.split('\n').map((_) => termIndent + _).join('\n').trimStart() -// + source.slice(termMarker.result.index - 1, typeMarker.index + 1) -// + extension.type.split('\n').map((_) => typeIndent + _).join('\n') -// + source.slice(typeMarker.result.index - 1) -// } \ No newline at end of file +function splice2(source: string, first: Splice, second: Splice) { + return '' + + source.slice(0, first.start) + + '\n' + ' '.repeat(first.firstLineOffset) + first.content.split('\n').map((_) => ' '.repeat(first.offset) + _).join().trimStart() + + source.slice(first.end, second.start) + + '\n' + ' '.repeat(second.firstLineOffset) + second.content.split('\n').map((_) => ' '.repeat(second.offset) + _).join('\n').trimStart() + + source.slice(second.end) +} diff --git a/packages/schema-generator/test/e2e.test.ts b/packages/schema-generator/test/e2e.test.ts index 09a1397f..a11aec8a 100644 --- a/packages/schema-generator/test/e2e.test.ts +++ b/packages/schema-generator/test/e2e.test.ts @@ -26,17 +26,16 @@ vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () = vi.it('〖️⛳️〗› ❲generated❳: optional schema', () => { vi.assert.isTrue(t.optional(t.integer)(void 0)) vi.assert.isTrue(t.optional(t.integer)(0)) - vi.assert.isFalse(t.optional(t.integer)('')) - vi.assert.isTrue(t.object({ a: t.optional(t.integer) })({})) vi.assert.isTrue(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({ b: { c: 0 } })) vi.assert.isTrue(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({ a: 0, b: { c: 1 } })) vi.assert.isTrue(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({})) + vi.assert.isFalse(t.optional(t.integer)('')) - configure({ schema: { optionalTreatment: 'presentButUndefinedIsOK' } }) + void configure({ schema: { optionalTreatment: 'presentButUndefinedIsOK' } }) vi.assert.isTrue(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({ b: void 0 })) - configure({ schema: { optionalTreatment: 'exactOptional' } }) + void configure({ schema: { optionalTreatment: 'exactOptional' } }) vi.assert.isFalse(t.object({ a: t.optional(t.integer), b: t.optional(t.object({ c: t.number })) })({ b: void 0 })) }) diff --git a/packages/schema-generator/test/generate.test.ts b/packages/schema-generator/test/generate.test.ts new file mode 100644 index 00000000..aeae3875 --- /dev/null +++ b/packages/schema-generator/test/generate.test.ts @@ -0,0 +1,226 @@ +import * as vi from 'vitest' +import * as path from 'node:path' +import * as fs from 'node:fs' + +import { writeSchemas } from '@traversable/schema-generator' + +/** + * ## TODO: + * - [ ] readonlyArray + * - [x] null + * - [x] void + * - [x] never + * - [x] unknown + * - [x] any + * - [x] undefined + * - [x] symbol + * - [x] boolean + * - [x] optional + * - [x] bigint + * - [x] eq + */ +let TODOs = void 0 + +let DIR_PATH = path.join(path.resolve(), 'packages', 'schema-generator', 'test') +let DATA_PATH = path.join(DIR_PATH, 'test-data') + +let PATH = { + __generated__: path.join(DIR_PATH, '__generated__'), + sources: { + never: { + core: path.join(DATA_PATH, 'never', 'core.ts'), + extension: path.join(DATA_PATH, 'never', 'extension.ts'), + equals: path.join(DATA_PATH, 'never', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'never', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'never', 'toString.ts'), + validate: path.join(DATA_PATH, 'never', 'validate.ts'), + }, + unknown: { + core: path.join(DATA_PATH, 'unknown', 'core.ts'), + extension: path.join(DATA_PATH, 'unknown', 'extension.ts'), + equals: path.join(DATA_PATH, 'unknown', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'unknown', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'unknown', 'toString.ts'), + validate: path.join(DATA_PATH, 'unknown', 'validate.ts'), + }, + any: { + core: path.join(DATA_PATH, 'any', 'core.ts'), + extension: path.join(DATA_PATH, 'any', 'extension.ts'), + equals: path.join(DATA_PATH, 'any', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'any', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'any', 'toString.ts'), + validate: path.join(DATA_PATH, 'any', 'validate.ts'), + }, + void: { + core: path.join(DATA_PATH, 'void', 'core.ts'), + extension: path.join(DATA_PATH, 'void', 'extension.ts'), + equals: path.join(DATA_PATH, 'void', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'void', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'void', 'toString.ts'), + validate: path.join(DATA_PATH, 'void', 'validate.ts'), + }, + null: { + core: path.join(DATA_PATH, 'null', 'core.ts'), + extension: path.join(DATA_PATH, 'null', 'extension.ts'), + equals: path.join(DATA_PATH, 'null', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'null', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'null', 'toString.ts'), + validate: path.join(DATA_PATH, 'null', 'validate.ts'), + }, + undefined: { + core: path.join(DATA_PATH, 'undefined', 'core.ts'), + extension: path.join(DATA_PATH, 'undefined', 'extension.ts'), + equals: path.join(DATA_PATH, 'undefined', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'undefined', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'undefined', 'toString.ts'), + validate: path.join(DATA_PATH, 'undefined', 'validate.ts'), + }, + boolean: { + core: path.join(DATA_PATH, 'boolean', 'core.ts'), + extension: path.join(DATA_PATH, 'boolean', 'extension.ts'), + equals: path.join(DATA_PATH, 'boolean', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'boolean', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'boolean', 'toString.ts'), + validate: path.join(DATA_PATH, 'boolean', 'validate.ts'), + }, + symbol: { + core: path.join(DATA_PATH, 'symbol', 'core.ts'), + extension: path.join(DATA_PATH, 'symbol', 'extension.ts'), + equals: path.join(DATA_PATH, 'symbol', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'symbol', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'symbol', 'toString.ts'), + validate: path.join(DATA_PATH, 'symbol', 'validate.ts'), + }, + integer: { + core: path.join(DATA_PATH, 'integer', 'core.ts'), + extension: path.join(DATA_PATH, 'integer', 'extension.ts'), + equals: path.join(DATA_PATH, 'integer', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'integer', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'integer', 'toString.ts'), + validate: path.join(DATA_PATH, 'integer', 'validate.ts'), + }, + bigint: { + core: path.join(DATA_PATH, 'bigint', 'core.ts'), + extension: path.join(DATA_PATH, 'bigint', 'extension.ts'), + equals: path.join(DATA_PATH, 'bigint', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'bigint', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'bigint', 'toString.ts'), + validate: path.join(DATA_PATH, 'bigint', 'validate.ts'), + }, + number: { + core: path.join(DATA_PATH, 'number', 'core.ts'), + extension: path.join(DATA_PATH, 'number', 'extension.ts'), + equals: path.join(DATA_PATH, 'number', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'number', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'number', 'toString.ts'), + validate: path.join(DATA_PATH, 'number', 'validate.ts'), + }, + string: { + core: path.join(DATA_PATH, 'string', 'core.ts'), + extension: path.join(DATA_PATH, 'string', 'extension.ts'), + equals: path.join(DATA_PATH, 'string', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'string', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'string', 'toString.ts'), + validate: path.join(DATA_PATH, 'string', 'validate.ts'), + }, + eq: { + core: path.join(DATA_PATH, 'eq', 'core.ts'), + extension: path.join(DATA_PATH, 'eq', 'extension.ts'), + equals: path.join(DATA_PATH, 'eq', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'eq', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'eq', 'toString.ts'), + validate: path.join(DATA_PATH, 'eq', 'validate.ts'), + }, + optional: { + core: path.join(DATA_PATH, 'optional', 'core.ts'), + extension: path.join(DATA_PATH, 'optional', 'extension.ts'), + equals: path.join(DATA_PATH, 'optional', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'optional', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'optional', 'toString.ts'), + validate: path.join(DATA_PATH, 'optional', 'validate.ts'), + }, + array: { + core: path.join(DATA_PATH, 'array', 'core.ts'), + extension: path.join(DATA_PATH, 'array', 'extension.ts'), + equals: path.join(DATA_PATH, 'array', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'array', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'array', 'toString.ts'), + validate: path.join(DATA_PATH, 'array', 'validate.ts'), + }, + record: { + core: path.join(DATA_PATH, 'record', 'core.ts'), + extension: path.join(DATA_PATH, 'record', 'extension.ts'), + equals: path.join(DATA_PATH, 'record', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'record', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'record', 'toString.ts'), + validate: path.join(DATA_PATH, 'record', 'validate.ts'), + }, + union: { + core: path.join(DATA_PATH, 'union', 'core.ts'), + extension: path.join(DATA_PATH, 'union', 'extension.ts'), + equals: path.join(DATA_PATH, 'union', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'union', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'union', 'toString.ts'), + validate: path.join(DATA_PATH, 'union', 'validate.ts'), + }, + intersect: { + core: path.join(DATA_PATH, 'intersect', 'core.ts'), + extension: path.join(DATA_PATH, 'intersect', 'extension.ts'), + equals: path.join(DATA_PATH, 'intersect', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'intersect', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'intersect', 'toString.ts'), + validate: path.join(DATA_PATH, 'intersect', 'validate.ts'), + }, + tuple: { + core: path.join(DATA_PATH, 'tuple', 'core.ts'), + extension: path.join(DATA_PATH, 'tuple', 'extension.ts'), + equals: path.join(DATA_PATH, 'tuple', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'tuple', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'tuple', 'toString.ts'), + validate: path.join(DATA_PATH, 'tuple', 'validate.ts'), + }, + object: { + core: path.join(DATA_PATH, 'object', 'core.ts'), + extension: path.join(DATA_PATH, 'object', 'extension.ts'), + equals: path.join(DATA_PATH, 'object', 'equals.ts'), + toJsonSchema: path.join(DATA_PATH, 'object', 'toJsonSchema.ts'), + toString: path.join(DATA_PATH, 'object', 'toString.ts'), + validate: path.join(DATA_PATH, 'object', 'validate.ts'), + }, + }, + targets: { + never: path.join(DIR_PATH, '__generated__', 'never.gen.ts'), + unknown: path.join(DIR_PATH, '__generated__', 'unknown.gen.ts'), + any: path.join(DIR_PATH, '__generated__', 'any.gen.ts'), + void: path.join(DIR_PATH, '__generated__', 'void.gen.ts'), + null: path.join(DIR_PATH, '__generated__', 'null.gen.ts'), + undefined: path.join(DIR_PATH, '__generated__', 'undefined.gen.ts'), + boolean: path.join(DIR_PATH, '__generated__', 'boolean.gen.ts'), + symbol: path.join(DIR_PATH, '__generated__', 'symbol.gen.ts'), + integer: path.join(DIR_PATH, '__generated__', 'integer.gen.ts'), + bigint: path.join(DIR_PATH, '__generated__', 'bigint.gen.ts'), + number: path.join(DIR_PATH, '__generated__', 'number.gen.ts'), + string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), + eq: path.join(DIR_PATH, '__generated__', 'eq.gen.ts'), + optional: path.join(DIR_PATH, '__generated__', 'optional.gen.ts'), + array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), + record: path.join(DIR_PATH, '__generated__', 'record.gen.ts'), + union: path.join(DIR_PATH, '__generated__', 'union.gen.ts'), + intersect: path.join(DIR_PATH, '__generated__', 'intersect.gen.ts'), + tuple: path.join(DIR_PATH, '__generated__', 'tuple.gen.ts'), + object: path.join(DIR_PATH, '__generated__', 'object.gen.ts'), + } +} + +vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { + vi.it('〖️⛳️〗› ❲writeSchemas❳', () => { + if (!fs.existsSync(DIR_PATH)) fs.mkdirSync(DIR_PATH) + if (!fs.existsSync(DATA_PATH)) fs.mkdirSync(DATA_PATH) + if (!fs.existsSync(PATH.__generated__)) fs.mkdirSync(PATH.__generated__) + + writeSchemas( + PATH.sources, + PATH.targets, + ) + }) +}) diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index 6821587d..211e1411 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -1,227 +1,5 @@ import * as vi from 'vitest' -import * as path from 'node:path' -import * as fs from 'node:fs' - -import { - deduplicateImports, - makeImport, - makeImports, - parseFile, - replaceExtensions, - writeSchemas, - parseSourceFile, -} from '@traversable/schema-generator' -import { fn } from '@traversable/registry' - -let DIR_PATH = path.join(path.resolve(), 'packages', 'schema-generator', 'test') -let DATA_PATH = path.join(DIR_PATH, 'test-data') - -/** - * ## TODO: - * - [ ] null - * - [ ] void - * - [ ] never - * - [ ] unknown - * - [ ] any - * - [ ] undefined - * - [ ] symbol - * - [ ] boolean - * - [ ] readonlyArray - * - [x] optional - * - [x] bigint - * - [x] eq - */ -let TODOs = void 0 - -let PATH = { - __generated__: path.join(DIR_PATH, '__generated__'), - sources: { - never: { - core: path.join(DATA_PATH, 'never', 'core.ts'), - extension: path.join(DATA_PATH, 'never', 'extension.ts'), - equals: path.join(DATA_PATH, 'never', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'never', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'never', 'toString.ts'), - validate: path.join(DATA_PATH, 'never', 'validate.ts'), - }, - unknown: { - core: path.join(DATA_PATH, 'unknown', 'core.ts'), - extension: path.join(DATA_PATH, 'unknown', 'extension.ts'), - equals: path.join(DATA_PATH, 'unknown', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'unknown', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'unknown', 'toString.ts'), - validate: path.join(DATA_PATH, 'unknown', 'validate.ts'), - }, - any: { - core: path.join(DATA_PATH, 'any', 'core.ts'), - extension: path.join(DATA_PATH, 'any', 'extension.ts'), - equals: path.join(DATA_PATH, 'any', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'any', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'any', 'toString.ts'), - validate: path.join(DATA_PATH, 'any', 'validate.ts'), - }, - void: { - core: path.join(DATA_PATH, 'void', 'core.ts'), - extension: path.join(DATA_PATH, 'void', 'extension.ts'), - equals: path.join(DATA_PATH, 'void', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'void', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'void', 'toString.ts'), - validate: path.join(DATA_PATH, 'void', 'validate.ts'), - }, - - null: { - core: path.join(DATA_PATH, 'null', 'core.ts'), - extension: path.join(DATA_PATH, 'null', 'extension.ts'), - equals: path.join(DATA_PATH, 'null', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'null', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'null', 'toString.ts'), - validate: path.join(DATA_PATH, 'null', 'validate.ts'), - }, - undefined: { - core: path.join(DATA_PATH, 'undefined', 'core.ts'), - extension: path.join(DATA_PATH, 'undefined', 'extension.ts'), - equals: path.join(DATA_PATH, 'undefined', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'undefined', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'undefined', 'toString.ts'), - validate: path.join(DATA_PATH, 'undefined', 'validate.ts'), - }, - - boolean: { - core: path.join(DATA_PATH, 'boolean', 'core.ts'), - extension: path.join(DATA_PATH, 'boolean', 'extension.ts'), - equals: path.join(DATA_PATH, 'boolean', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'boolean', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'boolean', 'toString.ts'), - validate: path.join(DATA_PATH, 'boolean', 'validate.ts'), - }, - symbol: { - core: path.join(DATA_PATH, 'symbol', 'core.ts'), - extension: path.join(DATA_PATH, 'symbol', 'extension.ts'), - equals: path.join(DATA_PATH, 'symbol', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'symbol', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'symbol', 'toString.ts'), - validate: path.join(DATA_PATH, 'symbol', 'validate.ts'), - }, - integer: { - core: path.join(DATA_PATH, 'integer', 'core.ts'), - extension: path.join(DATA_PATH, 'integer', 'extension.ts'), - equals: path.join(DATA_PATH, 'integer', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'integer', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'integer', 'toString.ts'), - validate: path.join(DATA_PATH, 'integer', 'validate.ts'), - }, - bigint: { - core: path.join(DATA_PATH, 'bigint', 'core.ts'), - extension: path.join(DATA_PATH, 'bigint', 'extension.ts'), - equals: path.join(DATA_PATH, 'bigint', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'bigint', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'bigint', 'toString.ts'), - validate: path.join(DATA_PATH, 'bigint', 'validate.ts'), - }, - number: { - core: path.join(DATA_PATH, 'number', 'core.ts'), - extension: path.join(DATA_PATH, 'number', 'extension.ts'), - equals: path.join(DATA_PATH, 'number', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'number', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'number', 'toString.ts'), - validate: path.join(DATA_PATH, 'number', 'validate.ts'), - }, - string: { - core: path.join(DATA_PATH, 'string', 'core.ts'), - extension: path.join(DATA_PATH, 'string', 'extension.ts'), - equals: path.join(DATA_PATH, 'string', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'string', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'string', 'toString.ts'), - validate: path.join(DATA_PATH, 'string', 'validate.ts'), - }, - eq: { - core: path.join(DATA_PATH, 'eq', 'core.ts'), - extension: path.join(DATA_PATH, 'eq', 'extension.ts'), - equals: path.join(DATA_PATH, 'eq', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'eq', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'eq', 'toString.ts'), - validate: path.join(DATA_PATH, 'eq', 'validate.ts'), - }, - optional: { - core: path.join(DATA_PATH, 'optional', 'core.ts'), - extension: path.join(DATA_PATH, 'optional', 'extension.ts'), - equals: path.join(DATA_PATH, 'optional', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'optional', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'optional', 'toString.ts'), - validate: path.join(DATA_PATH, 'optional', 'validate.ts'), - }, - array: { - core: path.join(DATA_PATH, 'array', 'core.ts'), - extension: path.join(DATA_PATH, 'array', 'extension.ts'), - equals: path.join(DATA_PATH, 'array', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'array', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'array', 'toString.ts'), - validate: path.join(DATA_PATH, 'array', 'validate.ts'), - }, - record: { - core: path.join(DATA_PATH, 'record', 'core.ts'), - extension: path.join(DATA_PATH, 'record', 'extension.ts'), - equals: path.join(DATA_PATH, 'record', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'record', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'record', 'toString.ts'), - validate: path.join(DATA_PATH, 'record', 'validate.ts'), - }, - union: { - core: path.join(DATA_PATH, 'union', 'core.ts'), - extension: path.join(DATA_PATH, 'union', 'extension.ts'), - equals: path.join(DATA_PATH, 'union', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'union', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'union', 'toString.ts'), - validate: path.join(DATA_PATH, 'union', 'validate.ts'), - }, - intersect: { - core: path.join(DATA_PATH, 'intersect', 'core.ts'), - extension: path.join(DATA_PATH, 'intersect', 'extension.ts'), - equals: path.join(DATA_PATH, 'intersect', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'intersect', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'intersect', 'toString.ts'), - validate: path.join(DATA_PATH, 'intersect', 'validate.ts'), - }, - tuple: { - core: path.join(DATA_PATH, 'tuple', 'core.ts'), - extension: path.join(DATA_PATH, 'tuple', 'extension.ts'), - equals: path.join(DATA_PATH, 'tuple', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'tuple', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'tuple', 'toString.ts'), - validate: path.join(DATA_PATH, 'tuple', 'validate.ts'), - }, - object: { - core: path.join(DATA_PATH, 'object', 'core.ts'), - extension: path.join(DATA_PATH, 'object', 'extension.ts'), - equals: path.join(DATA_PATH, 'object', 'equals.ts'), - toJsonSchema: path.join(DATA_PATH, 'object', 'toJsonSchema.ts'), - toString: path.join(DATA_PATH, 'object', 'toString.ts'), - validate: path.join(DATA_PATH, 'object', 'validate.ts'), - }, - }, - targets: { - never: path.join(DIR_PATH, '__generated__', 'never.gen.ts'), - unknown: path.join(DIR_PATH, '__generated__', 'unknown.gen.ts'), - any: path.join(DIR_PATH, '__generated__', 'any.gen.ts'), - void: path.join(DIR_PATH, '__generated__', 'void.gen.ts'), - null: path.join(DIR_PATH, '__generated__', 'null.gen.ts'), - undefined: path.join(DIR_PATH, '__generated__', 'undefined.gen.ts'), - boolean: path.join(DIR_PATH, '__generated__', 'boolean.gen.ts'), - symbol: path.join(DIR_PATH, '__generated__', 'symbol.gen.ts'), - integer: path.join(DIR_PATH, '__generated__', 'integer.gen.ts'), - bigint: path.join(DIR_PATH, '__generated__', 'bigint.gen.ts'), - number: path.join(DIR_PATH, '__generated__', 'number.gen.ts'), - string: path.join(DIR_PATH, '__generated__', 'string.gen.ts'), - eq: path.join(DIR_PATH, '__generated__', 'eq.gen.ts'), - optional: path.join(DIR_PATH, '__generated__', 'optional.gen.ts'), - array: path.join(DIR_PATH, '__generated__', 'array.gen.ts'), - record: path.join(DIR_PATH, '__generated__', 'record.gen.ts'), - union: path.join(DIR_PATH, '__generated__', 'union.gen.ts'), - intersect: path.join(DIR_PATH, '__generated__', 'intersect.gen.ts'), - tuple: path.join(DIR_PATH, '__generated__', 'tuple.gen.ts'), - object: path.join(DIR_PATH, '__generated__', 'object.gen.ts'), - } -} +import { deduplicateImports, makeImport } from '@traversable/schema-generator' vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { vi.it('〖️⛳️〗› ❲makeImport❳', () => { @@ -382,54 +160,4 @@ vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () = } `) }) - - - vi.it('〖️⛳️〗› ❲makeImports❳', () => { - // let src = { - // core: parseFile(PATH.sources.string.core), - // extension: parseFile(PATH.sources.string.extension), - // equals: parseFile(PATH.sources.string.equals), - // toJsonSchema: parseFile(PATH.sources.string.toJsonSchema), - // toString: parseFile(PATH.sources.string.toString), - // validate: parseFile(PATH.sources.string.validate), - // } - - // vi.expect(makeImports({ string: fn.map(src, (file) => file.imports) })).toMatchInlineSnapshot(` - // { - // "string": "import type { - // Equal, - // Force, - // Integer, - // PickIfDefined, - // Unknown - // } from '@traversable/registry' - // import { - // has, - // Math_max, - // Math_min, - // Object_assign, - // URI - // } from '@traversable/registry' - // import type { Bounds, t } from '@traversable/schema' - // import { __carryover as carryover, __within as within } from '@traversable/schema' - // import type { SizeBounds } from '@traversable/schema-to-json-schema' - // import type { ValidationError, ValidationFn } from '@traversable/derive-validators' - // import { NullaryErrors } from '@traversable/derive-validators'", - // } - // `) - }) -}) - -vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { - vi.it('〖️⛳️〗› ❲writeSchemas❳', () => { - if (!fs.existsSync(DIR_PATH)) fs.mkdirSync(DIR_PATH) - if (!fs.existsSync(DATA_PATH)) fs.mkdirSync(DATA_PATH) - if (!fs.existsSync(PATH.__generated__)) fs.mkdirSync(PATH.__generated__) - - writeSchemas( - PATH.sources, - PATH.targets, - ) - }) }) - diff --git a/packages/schema/src/postinstall.ts b/packages/schema/src/postinstall.ts new file mode 100644 index 00000000..e69de29b diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88e1b1eb..1ac3945d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,7 +64,7 @@ importers: version: 5.8.2 vitest: specifier: ^3.0.4 - version: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(yaml@2.7.1) + version: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.2))(yaml@2.7.1) bin: dependencies: @@ -260,6 +260,9 @@ importers: packages/schema-generator: devDependencies: + '@clack/prompts': + specifier: ^0.10.1 + version: 0.10.1 '@traversable/derive-validators': specifier: workspace:^ version: link:../derive-validators/dist @@ -275,6 +278,9 @@ importers: '@traversable/schema-to-string': specifier: workspace:^ version: link:../schema-to-string/dist + picocolors: + specifier: ^1.1.1 + version: 1.1.1 publishDirectory: dist packages/schema-seed: @@ -478,6 +484,15 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} + + '@bundled-es-modules/statuses@1.0.1': + resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} + + '@bundled-es-modules/tough-cookie@0.1.6': + resolution: {integrity: sha512-dvMHbL464C0zI+Yqxbz6kZ5TOEp7GLW+pry/RWndAR8MJQAXZ2rPmIs8tziTZjeIyhSNZgZbCePtfSbdWqStJw==} + '@changesets/apply-release-plan@7.0.10': resolution: {integrity: sha512-wNyeIJ3yDsVspYvHnEz1xQDq18D9ifed3lI+wxRQRK4pArUcuHgCTrHv0QRnnwjhVCQACxZ+CBih3wgOct6UXw==} @@ -539,6 +554,12 @@ packages: '@changesets/write@0.4.0': resolution: {integrity: sha512-CdTLvIOPiCNuH71pyDu3rA+Q0n65cmAbXnwWH84rKGiFumFzkmHNT8KHTMEchcxN+Kl8I54xGUhJ7l3E7X396Q==} + '@clack/core@0.4.2': + resolution: {integrity: sha512-NYQfcEy8MWIxrT5Fj8nIVchfRFA26yYKJcvBS7WlUIlw2OmQOY9DhGGXMovyI5J5PpxrCPGkgUi207EBrjpBvg==} + + '@clack/prompts@0.10.1': + resolution: {integrity: sha512-Q0T02vx8ZM9XSv9/Yde0jTmmBQufZhPJfYAg2XrrrxWWaZgq1rr8nU8Hv710BQ1dhoP8rtY7YUdpGej2Qza/cw==} + '@dependents/detective-less@5.0.1': resolution: {integrity: sha512-Y6+WUMsTFWE5jb20IFP4YGa5IrGY/+a/FbOSjDF/wz9gepU2hwCYSXRHP/vPwBvwcY3SVMASt4yXxbXNXigmZQ==} engines: {node: '>=18'} @@ -810,6 +831,37 @@ packages: resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==} engines: {node: '>=18.18'} + '@inquirer/confirm@5.1.9': + resolution: {integrity: sha512-NgQCnHqFTjF7Ys2fsqK2WtnA8X1kHyInyG+nMIuHowVTIgIuS10T4AznI/PvbqSpJqjCUqNBlKGh1v3bwLFL4w==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/core@10.1.10': + resolution: {integrity: sha512-roDaKeY1PYY0aCqhRmXihrHjoSW2A00pV3Ke5fTpMCkzcGF64R8e0lw3dK+eLEHwS4vB5RnW1wuQmvzoRul8Mw==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + + '@inquirer/figures@1.0.11': + resolution: {integrity: sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==} + engines: {node: '>=18'} + + '@inquirer/type@3.0.6': + resolution: {integrity: sha512-/mKVCtVpyBu3IDarv0G+59KC4stsD5mDsGpYh+GKs1NZT88Jh52+cuoA1AtLk2Q0r/quNl+1cSUyLRHBFeD0XA==} + engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' + peerDependenciesMeta: + '@types/node': + optional: true + '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} @@ -842,6 +894,10 @@ packages: '@manypkg/get-packages@1.1.3': resolution: {integrity: sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A==} + '@mswjs/interceptors@0.37.6': + resolution: {integrity: sha512-wK+5pLK5XFmgtH3aQ2YVvA3HohS3xqV/OxuVOdNx9Wpnz7VE/fnC+e1A7ln6LFYeck7gOJ/dsZV6OLplOtAJ2w==} + engines: {node: '>=18'} + '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': resolution: {integrity: sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==} @@ -857,6 +913,15 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@open-draft/deferred-promise@2.2.0': + resolution: {integrity: sha512-CecwLWx3rhxVQF6V4bAgPS5t+So2sTbPgAzafKkVizyi7tlwpcFpdFqq+wqF2OwNBmqFuu6tOyouTuxgpMfzmA==} + + '@open-draft/logger@0.3.0': + resolution: {integrity: sha512-X2g45fzhxH238HKO4xbSr7+wBS8Fvw6ixhTDuvLd5mqh6bJJCFAPwU9mPDxbcrRtfxv4u5IHCEH77BmxvXmmxQ==} + + '@open-draft/until@2.1.0': + resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} + '@parcel/watcher-android-arm64@2.5.1': resolution: {integrity: sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==} engines: {node: '>= 10.0.0'} @@ -1188,6 +1253,12 @@ packages: '@types/react@19.1.0': resolution: {integrity: sha512-UaicktuQI+9UKyA4njtDOGBD/67t8YEBt2xdfqu8+gP9hqPUPsiXlNPcpS2gVdjmis5GKPG3fCxbQLVgxsQZ8w==} + '@types/statuses@2.0.5': + resolution: {integrity: sha512-jmIUGWrAiwu3dZpxntxieC+1n/5c3mjrImkmOSQ2NC5uP6cYO4aAZDdSmRcI5C1oiTmqlZGHC+/NmJrKogbP5A==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + '@typescript-eslint/eslint-plugin@8.29.0': resolution: {integrity: sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1335,6 +1406,10 @@ packages: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1487,9 +1562,17 @@ packages: resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} engines: {node: '>=6'} + cli-width@4.1.0: + resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} + engines: {node: '>= 12'} + cliui@7.0.4: resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + clone@1.0.4: resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} engines: {node: '>=0.8'} @@ -1957,6 +2040,10 @@ packages: graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + graphql@16.10.0: + resolution: {integrity: sha512-AjqGKbDGUFRKIRCP9tCKiIGHyriz2oHEbPIbEtcSLSs4YjReZOIPQQWek4+6hjw62H9QShXHyaGivGiYVLeYFQ==} + engines: {node: ^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1976,6 +2063,9 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + headers-polyfill@4.0.3: + resolution: {integrity: sha512-IScLbePpkvO846sIwOtOTDjutRMWdXdJmXdMvk6gCBHxFO8d+QKOQedyZSxFTTFYRSmlgSTDtXqqq4pcenBXLQ==} + html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} @@ -2052,6 +2142,9 @@ packages: resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} engines: {node: '>=8'} + is-node-process@1.2.0: + resolution: {integrity: sha512-Vg4o6/fqPxIjtxgUH5QLJhwZ7gW5diGCVlXpuUfELC62CuxM1iHcRe51f2W1FDy04Ai4KJkagKjx3XaqyfRKXw==} + is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} @@ -2295,9 +2388,23 @@ packages: ms@2.1.3: resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + msw@2.7.3: + resolution: {integrity: sha512-+mycXv8l2fEAjFZ5sjrtjJDmm2ceKGjrNbBr1durRg6VkU9fNUE/gsmQ51hWbHqs+l35W1iM+ZsmOD9Fd6lspw==} + engines: {node: '>=18'} + hasBin: true + peerDependencies: + typescript: 5.8.2 + peerDependenciesMeta: + typescript: + optional: true + multipasta@0.2.5: resolution: {integrity: sha512-c8eMDb1WwZcE02WVjHoOmUVk7fnKU/RmUcosHACglrWAuPQsEJv+E8430sXj6jNc1jHw0zrS16aCjQh4BcEb4A==} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -2351,6 +2458,9 @@ packages: outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} + outvariant@1.4.3: + resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} + p-filter@2.1.0: resolution: {integrity: sha512-ZBxxZ5sL2HghephhpGAQdoskxplTwr7ICaehZwLIlfL6acuVgZPm8yBNuRAFBGEqtD/hmUeq9eqLg2ys9Xr/yw==} engines: {node: '>=8'} @@ -2416,6 +2526,9 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2483,6 +2596,9 @@ packages: resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} engines: {node: '>=10'} + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2496,6 +2612,9 @@ packages: quansync@0.2.10: resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==} + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} @@ -2551,6 +2670,9 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + resolve-dependency-path@4.0.1: resolution: {integrity: sha512-YQftIIC4vzO9UMhO/sCgXukNyiwVRCVaxiWskCBy7Zpqkplm8kTAISZ8O1MoKW1ca6xzgLUBjZTcDgypXvXxiQ==} engines: {node: '>=18'} @@ -2644,6 +2766,9 @@ packages: resolution: {integrity: sha512-FoqMu0NCGBLCcAkS1qA+XJIQTR6/JHfQXl+uGteNCQ76T91DMUjPa9xfmeqMY3z80nLSg9yQmNjK0Px6RWsH/A==} engines: {node: '>=18'} + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + slash@2.0.0: resolution: {integrity: sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==} engines: {node: '>=6'} @@ -2679,6 +2804,10 @@ packages: stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + statuses@2.0.1: + resolution: {integrity: sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==} + engines: {node: '>= 0.8'} + std-env@3.9.0: resolution: {integrity: sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==} @@ -2688,6 +2817,9 @@ packages: stream-to-array@2.3.0: resolution: {integrity: sha512-UsZtOYEn4tWU2RGLOXr/o/xjRBftZRlG3dEWoaHr8j4GuypJ3isitGbVyjQKAuMu+xbiop8q224TjiZWc4XTZA==} + strict-event-emitter@0.5.1: + resolution: {integrity: sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2792,6 +2924,10 @@ packages: resolution: {integrity: sha512-sf4i37nQ2LBx4m3wB74y+ubopq6W/dIzXg0FDGjsYnZHVa1Da8FH853wlL2gtUhg+xJXjfk3kUZS3BRoQeoQBQ==} engines: {node: '>=6'} + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -2820,6 +2956,14 @@ packages: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.39.1: + resolution: {integrity: sha512-uW9qzd66uyHYxwyVBYiwS4Oi0qZyUqwjU+Oevr6ZogYiXt99EOYtwvzMSLw1c3lYo2HzJsep/NB23iEVEgjG/w==} + engines: {node: '>=16'} + typescript-eslint@8.29.0: resolution: {integrity: sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2846,6 +2990,10 @@ packages: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'} + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + update-browserslist-db@1.1.3: resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} hasBin: true @@ -2855,6 +3003,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + use-sync-external-store@1.5.0: resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==} peerDependencies: @@ -2985,6 +3136,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@6.2.0: + resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} + engines: {node: '>=8'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -3024,14 +3179,26 @@ packages: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + yargs@16.2.0: resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} engines: {node: '>=10'} + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} + engines: {node: '>=18'} + zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} @@ -3208,6 +3375,22 @@ snapshots: '@bcoe/v8-coverage@1.0.2': {} + '@bundled-es-modules/cookie@2.0.1': + dependencies: + cookie: 0.7.2 + optional: true + + '@bundled-es-modules/statuses@1.0.1': + dependencies: + statuses: 2.0.1 + optional: true + + '@bundled-es-modules/tough-cookie@0.1.6': + dependencies: + '@types/tough-cookie': 4.0.5 + tough-cookie: 4.1.4 + optional: true + '@changesets/apply-release-plan@7.0.10': dependencies: '@changesets/config': 3.1.1 @@ -3365,6 +3548,17 @@ snapshots: human-id: 4.1.1 prettier: 2.8.8 + '@clack/core@0.4.2': + dependencies: + picocolors: 1.1.1 + sisteransi: 1.0.5 + + '@clack/prompts@0.10.1': + dependencies: + '@clack/core': 0.4.2 + picocolors: 1.1.1 + sisteransi: 1.0.5 + '@dependents/detective-less@5.0.1': dependencies: gonzales-pe: 4.3.0 @@ -3553,7 +3747,7 @@ snapshots: '@fast-check/vitest@0.2.0(vitest@3.1.1)': dependencies: fast-check: 3.23.2 - vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(yaml@2.7.1) + vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.2))(yaml@2.7.1) '@humanfs/core@0.19.1': {} @@ -3568,6 +3762,36 @@ snapshots: '@humanwhocodes/retry@0.4.2': {} + '@inquirer/confirm@5.1.9(@types/node@22.14.0)': + dependencies: + '@inquirer/core': 10.1.10(@types/node@22.14.0) + '@inquirer/type': 3.0.6(@types/node@22.14.0) + optionalDependencies: + '@types/node': 22.14.0 + optional: true + + '@inquirer/core@10.1.10(@types/node@22.14.0)': + dependencies: + '@inquirer/figures': 1.0.11 + '@inquirer/type': 3.0.6(@types/node@22.14.0) + ansi-escapes: 4.3.2 + cli-width: 4.1.0 + mute-stream: 2.0.0 + signal-exit: 4.1.0 + wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + optionalDependencies: + '@types/node': 22.14.0 + optional: true + + '@inquirer/figures@1.0.11': + optional: true + + '@inquirer/type@3.0.6(@types/node@22.14.0)': + optionalDependencies: + '@types/node': 22.14.0 + optional: true + '@isaacs/cliui@8.0.2': dependencies: string-width: 5.1.2 @@ -3612,6 +3836,16 @@ snapshots: globby: 11.1.0 read-yaml-file: 1.1.0 + '@mswjs/interceptors@0.37.6': + dependencies: + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/logger': 0.3.0 + '@open-draft/until': 2.1.0 + is-node-process: 1.2.0 + outvariant: 1.4.3 + strict-event-emitter: 0.5.1 + optional: true + '@nicolo-ribaudo/chokidar-2@2.1.8-no-fsevents.3': optional: true @@ -3627,6 +3861,18 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.19.1 + '@open-draft/deferred-promise@2.2.0': + optional: true + + '@open-draft/logger@0.3.0': + dependencies: + is-node-process: 1.2.0 + outvariant: 1.4.3 + optional: true + + '@open-draft/until@2.1.0': + optional: true + '@parcel/watcher-android-arm64@2.5.1': optional: true @@ -3914,6 +4160,12 @@ snapshots: dependencies: csstype: 3.1.3 + '@types/statuses@2.0.5': + optional: true + + '@types/tough-cookie@4.0.5': + optional: true + '@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.8.2))(eslint@9.24.0)(typescript@5.8.2)': dependencies: '@eslint-community/regexpp': 4.12.1 @@ -4034,7 +4286,7 @@ snapshots: std-env: 3.9.0 test-exclude: 7.0.1 tinyrainbow: 2.0.0 - vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(yaml@2.7.1) + vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.2))(yaml@2.7.1) transitivePeerDependencies: - supports-color @@ -4045,12 +4297,13 @@ snapshots: chai: 5.2.0 tinyrainbow: 2.0.0 - '@vitest/mocker@3.1.1(vite@6.2.5(@types/node@22.14.0)(yaml@2.7.1))': + '@vitest/mocker@3.1.1(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.2))(vite@6.2.5(@types/node@22.14.0)(yaml@2.7.1))': dependencies: '@vitest/spy': 3.1.1 estree-walker: 3.0.3 magic-string: 0.30.17 optionalDependencies: + msw: 2.7.3(@types/node@22.14.0)(typescript@5.8.2) vite: 6.2.5(@types/node@22.14.0)(yaml@2.7.1) '@vitest/pretty-format@3.1.1': @@ -4081,7 +4334,7 @@ snapshots: sirv: 3.0.1 tinyglobby: 0.2.12 tinyrainbow: 2.0.0 - vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(yaml@2.7.1) + vitest: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.2))(yaml@2.7.1) '@vitest/utils@3.1.1': dependencies: @@ -4145,6 +4398,11 @@ snapshots: ansi-colors@4.1.3: {} + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + optional: true + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -4299,12 +4557,22 @@ snapshots: cli-spinners@2.9.2: {} + cli-width@4.1.0: + optional: true + cliui@7.0.4: dependencies: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + optional: true + clone@1.0.4: {} color-convert@2.0.1: @@ -4808,6 +5076,9 @@ snapshots: graphemer@1.4.0: {} + graphql@16.10.0: + optional: true + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -4824,6 +5095,9 @@ snapshots: dependencies: function-bind: 1.1.2 + headers-polyfill@4.0.3: + optional: true + html-escaper@2.0.2: {} human-id@4.1.1: {} @@ -4887,6 +5161,9 @@ snapshots: is-interactive@1.0.0: {} + is-node-process@1.2.0: + optional: true + is-number@7.0.0: {} is-obj@1.0.1: {} @@ -5110,8 +5387,37 @@ snapshots: ms@2.1.3: {} + msw@2.7.3(@types/node@22.14.0)(typescript@5.8.2): + dependencies: + '@bundled-es-modules/cookie': 2.0.1 + '@bundled-es-modules/statuses': 1.0.1 + '@bundled-es-modules/tough-cookie': 0.1.6 + '@inquirer/confirm': 5.1.9(@types/node@22.14.0) + '@mswjs/interceptors': 0.37.6 + '@open-draft/deferred-promise': 2.2.0 + '@open-draft/until': 2.1.0 + '@types/cookie': 0.6.0 + '@types/statuses': 2.0.5 + graphql: 16.10.0 + headers-polyfill: 4.0.3 + is-node-process: 1.2.0 + outvariant: 1.4.3 + path-to-regexp: 6.3.0 + picocolors: 1.1.1 + strict-event-emitter: 0.5.1 + type-fest: 4.39.1 + yargs: 17.7.2 + optionalDependencies: + typescript: 5.8.2 + transitivePeerDependencies: + - '@types/node' + optional: true + multipasta@0.2.5: {} + mute-stream@2.0.0: + optional: true + nanoid@3.3.11: {} natural-compare@1.4.0: {} @@ -5164,6 +5470,9 @@ snapshots: outdent@0.5.0: {} + outvariant@1.4.3: + optional: true + p-filter@2.1.0: dependencies: p-map: 2.1.0 @@ -5218,6 +5527,9 @@ snapshots: lru-cache: 11.1.0 minipass: 7.1.2 + path-to-regexp@6.3.0: + optional: true + path-type@4.0.0: {} pathe@2.0.3: {} @@ -5279,6 +5591,11 @@ snapshots: dependencies: parse-ms: 2.1.0 + psl@1.15.0: + dependencies: + punycode: 2.3.1 + optional: true + punycode@2.3.1: {} pure-rand@6.1.0: {} @@ -5287,6 +5604,9 @@ snapshots: quansync@0.2.10: {} + querystringify@2.2.0: + optional: true + queue-microtask@1.2.3: {} quote-unquote@1.0.0: {} @@ -5338,6 +5658,9 @@ snapshots: requirejs@2.3.7: {} + requires-port@1.0.0: + optional: true + resolve-dependency-path@4.0.1: {} resolve-from@4.0.0: {} @@ -5439,6 +5762,8 @@ snapshots: mrmime: 2.0.1 totalist: 3.0.1 + sisteransi@1.0.5: {} + slash@2.0.0: {} slash@3.0.0: {} @@ -5467,6 +5792,9 @@ snapshots: stackback@0.0.2: {} + statuses@2.0.1: + optional: true + std-env@3.9.0: {} stream-slice@0.1.2: {} @@ -5475,6 +5803,9 @@ snapshots: dependencies: any-promise: 1.3.0 + strict-event-emitter@0.5.1: + optional: true + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -5564,6 +5895,14 @@ snapshots: totalist@3.0.1: {} + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + optional: true + tr46@0.0.3: {} treeify@1.1.0: {} @@ -5591,6 +5930,12 @@ snapshots: dependencies: prelude-ls: 1.2.1 + type-fest@0.21.3: + optional: true + + type-fest@4.39.1: + optional: true + typescript-eslint@8.29.0(eslint@9.24.0)(typescript@5.8.2): dependencies: '@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.24.0)(typescript@5.8.2))(eslint@9.24.0)(typescript@5.8.2) @@ -5611,6 +5956,9 @@ snapshots: universalify@0.1.2: {} + universalify@0.2.0: + optional: true + update-browserslist-db@1.1.3(browserslist@4.24.4): dependencies: browserslist: 4.24.4 @@ -5621,6 +5969,12 @@ snapshots: dependencies: punycode: 2.3.1 + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + optional: true + use-sync-external-store@1.5.0(react@19.1.0): dependencies: react: 19.1.0 @@ -5670,10 +6024,10 @@ snapshots: fsevents: 2.3.3 yaml: 2.7.1 - vitest@3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(yaml@2.7.1): + vitest@3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.2))(yaml@2.7.1): dependencies: '@vitest/expect': 3.1.1 - '@vitest/mocker': 3.1.1(vite@6.2.5(@types/node@22.14.0)(yaml@2.7.1)) + '@vitest/mocker': 3.1.1(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.2))(vite@6.2.5(@types/node@22.14.0)(yaml@2.7.1)) '@vitest/pretty-format': 3.1.1 '@vitest/runner': 3.1.1 '@vitest/snapshot': 3.1.1 @@ -5751,6 +6105,13 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@6.2.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + optional: true + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -5775,6 +6136,9 @@ snapshots: yargs-parser@20.2.9: {} + yargs-parser@21.1.1: + optional: true + yargs@16.2.0: dependencies: cliui: 7.0.4 @@ -5785,6 +6149,20 @@ snapshots: y18n: 5.0.8 yargs-parser: 20.2.9 + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + optional: true + yocto-queue@0.1.0: {} + yoctocolors-cjs@2.1.2: + optional: true + zod@3.24.2: {} diff --git a/vite.config.ts b/vite.config.ts index 9bdd87a1..e2f0adb1 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -45,6 +45,7 @@ export default defineConfig({ // include: ['test/**/*.test.ts'], printConsoleTrace: true, sequence: { concurrent: true }, + slowTestThreshold: 400, workspace: [ 'examples/*', 'packages/*', From 06ccba683c0625e43bd0343b018e6f4e6f1b276d Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 12 Apr 2025 11:37:02 -0500 Subject: [PATCH 23/45] init(schema-core): breaks `@traversable/schema` into separate `@traversable/core` package --- README.md | 53 +- bin/constants.ts | 2 +- bin/docs.ts | 12 +- bin/process.ts | 20 +- bin/watch.ts | 1061 ----------------- config/__generated__/package-list.ts | 2 +- examples/sandbox/package.json | 2 +- examples/sandbox/src/demo/advanced.tsx | 2 +- examples/sandbox/src/demo/codec.ts | 9 +- .../src/demo/inferredTypePredicates.ts | 2 +- examples/sandbox/src/global.d.ts | 2 +- examples/sandbox/src/lib/functor.tsx | 11 +- examples/sandbox/src/lib/index.ts | 48 +- examples/sandbox/src/lib/map.ts | 6 +- examples/sandbox/src/lib/namespace.ts | 4 +- examples/sandbox/src/lib/prototype.ts | 4 +- examples/sandbox/src/lib/react.ts | 2 +- examples/sandbox/src/lib/set.ts | 4 +- examples/sandbox/src/lib/shared.ts | 2 +- examples/sandbox/src/lib/toHtml.tsx | 2 +- examples/sandbox/tsconfig.app.json | 4 +- examples/sandbox/vite.config.ts | 2 +- packages/derive-codec/package.json | 6 +- .../src/__generated__/__manifest__.ts | 6 +- packages/derive-codec/src/bind.ts | 77 -- packages/derive-codec/src/codec.ts | 46 +- packages/derive-codec/src/exports.ts | 1 - packages/derive-codec/src/install.ts | 82 +- packages/derive-codec/test/codec.test.ts | 5 +- packages/derive-codec/test/install.test.ts | 45 +- packages/derive-codec/tsconfig.build.json | 2 +- packages/derive-codec/tsconfig.src.json | 2 +- packages/derive-codec/tsconfig.test.json | 2 +- packages/derive-equals/package.json | 6 +- .../src/__generated__/__manifest__.ts | 6 +- packages/derive-equals/src/equals.ts | 2 +- packages/derive-equals/src/install.ts | 79 +- packages/derive-equals/test/equals.test.ts | 2 +- packages/derive-equals/test/install.test.ts | 37 +- packages/derive-equals/tsconfig.build.json | 2 +- packages/derive-equals/tsconfig.src.json | 2 +- packages/derive-equals/tsconfig.test.json | 2 +- packages/derive-validators/package.json | 4 +- .../src/__generated__/__manifest__.ts | 4 +- packages/derive-validators/src/errors.ts | 2 +- packages/derive-validators/src/install.ts | 100 +- packages/derive-validators/src/recursive.ts | 2 +- packages/derive-validators/src/shared.ts | 4 +- .../src/{prototype.ts => validate.ts} | 149 ++- packages/derive-validators/test/bind.test.ts | 2 +- .../derive-validators/test/install.test.ts | 2 +- .../derive-validators/test/validators.test.ts | 2 +- .../derive-validators/tsconfig.build.json | 2 +- packages/derive-validators/tsconfig.src.json | 2 +- packages/derive-validators/tsconfig.test.json | 2 +- packages/registry/src/equals.ts | 4 +- packages/registry/src/exports.ts | 10 + packages/registry/src/globalThis.ts | 13 +- packages/registry/src/parseArgs.ts | 2 +- packages/registry/src/predicate.ts | 4 + packages/registry/src/satisfies.ts | 7 + packages/registry/src/uri.ts | 2 +- packages/{schema => schema-core}/CHANGELOG.md | 0 packages/{schema => schema-core}/README.md | 39 +- packages/{schema => schema-core}/package.json | 4 +- .../src/__generated__/__manifest__.ts | 4 +- .../src/__schema.ts__} | 203 +--- packages/schema-core/src/any.ts | 36 + packages/schema-core/src/array.ts | 117 ++ packages/schema-core/src/bigint.ts | 96 ++ packages/schema-core/src/boolean.ts | 37 + .../{schema => schema-core}/src/bounded.ts | 10 + packages/schema-core/src/clone.ts | 9 + .../src/combinators.ts | 13 +- packages/schema-core/src/core.ts | 142 +++ packages/{schema => schema-core}/src/enum.ts | 12 +- packages/schema-core/src/eq.ts | 41 + .../{schema => schema-core}/src/equals.ts | 0 .../{schema => schema-core}/src/exports.ts | 19 +- packages/schema-core/src/extensions.ts | 29 + packages/{schema => schema-core}/src/has.ts | 14 +- packages/{schema => schema-core}/src/index.ts | 0 packages/schema-core/src/integer.ts | 100 ++ packages/schema-core/src/intersect.ts | 44 + packages/schema-core/src/key.ts | 6 + .../src/types.ts => schema-core/src/label.ts} | 73 +- packages/schema-core/src/namespace.ts | 81 ++ packages/schema-core/src/never.ts | 37 + packages/schema-core/src/nonnullable.ts | 38 + packages/schema-core/src/null.ts | 39 + packages/schema-core/src/number.ts | 133 +++ packages/schema-core/src/object.ts | 80 ++ packages/schema-core/src/of.ts | 44 + packages/schema-core/src/optional.ts | 48 + .../src/postinstall.ts | 0 .../{schema => schema-core}/src/predicates.ts | 20 +- packages/schema-core/src/readonlyArray.ts | 16 + packages/schema-core/src/record.ts | 44 + .../{schema => schema-core}/src/recursive.ts | 9 +- packages/schema-core/src/string.ts | 97 ++ packages/schema-core/src/symbol.ts | 36 + packages/schema-core/src/tuple.ts | 90 ++ packages/schema-core/src/types.ts | 190 +++ packages/schema-core/src/undefined.ts | 36 + packages/schema-core/src/union.ts | 44 + packages/schema-core/src/unknown.ts | 36 + packages/{schema => schema-core}/src/utils.ts | 48 +- .../{schema => schema-core}/src/version.ts | 0 packages/schema-core/src/void.ts | 36 + .../test/bounded.test.ts | 4 +- packages/schema-core/test/combinators.test.ts | 69 ++ .../{schema => schema-core}/test/enum.test.ts | 4 +- .../test/equals.bench.ts | 2 +- .../test/equals.test.ts | 4 +- .../{schema => schema-core}/test/has.test.ts | 4 +- .../test/inline.test.ts | 7 +- packages/schema-core/test/intersect.test.ts | 8 + packages/schema-core/test/nonnullable.test.ts | 15 + packages/schema-core/test/optional.test.ts | 16 + .../test/predicates.test.ts | 4 +- .../test/recursive.test.ts | 4 +- .../test/schema.test.ts | 16 +- packages/{schema => schema-core}/test/seed.ts | 27 +- .../test/utils.test.ts | 4 +- .../test/version.test.ts | 4 +- .../tsconfig.build.json | 0 .../{schema => schema-core}/tsconfig.json | 0 .../{schema => schema-core}/tsconfig.src.json | 0 .../tsconfig.test.json | 0 .../{schema => schema-core}/vitest.config.ts | 0 packages/schema-generator/package.json | 4 +- .../src/__generated__/__manifest__.ts | 4 +- packages/schema-generator/src/cli.ts | 6 +- packages/schema-generator/src/imports.ts | 4 +- .../test/{e2e.test.ts => __e2e.test.ts__} | 2 +- .../schema-generator/test/imports.test.ts | 22 +- packages/schema-generator/test/namespace.ts | 20 +- .../test/test-data/any/validate.ts | 2 +- .../test/test-data/array/core.ts | 7 +- .../test/test-data/array/equals.ts | 2 +- .../test/test-data/array/toJsonSchema.ts | 2 +- .../test/test-data/array/toString.ts | 2 +- .../test/test-data/array/validate.ts | 2 +- .../test/test-data/bigint/core.ts | 4 +- .../test/test-data/bigint/toJsonSchema.ts | 2 - .../test/test-data/bigint/validate.ts | 2 +- .../test/test-data/boolean/validate.ts | 2 +- .../test/test-data/eq/core.ts | 6 +- .../test/test-data/eq/equals.ts | 2 +- .../test/test-data/eq/toJsonSchema.ts | 2 +- .../test/test-data/eq/toString.ts | 2 +- .../test/test-data/eq/validate.ts | 2 +- .../test/test-data/integer/core.ts | 4 +- .../test/test-data/integer/toJsonSchema.ts | 2 +- .../test/test-data/integer/validate.ts | 2 +- .../test/test-data/intersect/core.ts | 8 +- .../test/test-data/intersect/equals.ts | 2 +- .../test/test-data/intersect/toJsonSchema.ts | 2 +- .../test/test-data/intersect/toString.ts | 2 +- .../test/test-data/intersect/validate.ts | 2 +- .../test/test-data/never/validate.ts | 2 +- .../test/test-data/null/validate.ts | 2 +- .../test/test-data/number/core.ts | 4 +- .../test/test-data/number/equals.ts | 5 +- .../test/test-data/number/toJsonSchema.ts | 2 +- .../test/test-data/number/validate.ts | 2 +- .../test/test-data/object/core.ts | 7 +- .../test/test-data/object/equals.ts | 2 +- .../test/test-data/object/toJsonSchema.ts | 2 +- .../test/test-data/object/toString.ts | 2 +- .../test/test-data/object/validate.ts | 2 +- .../test/test-data/optional/core.ts | 6 +- .../test/test-data/optional/equals.ts | 2 +- .../test/test-data/optional/toJsonSchema.ts | 2 +- .../test/test-data/optional/toString.ts | 2 +- .../test/test-data/optional/validate.ts | 2 +- .../test/test-data/record/core.ts | 6 +- .../test/test-data/record/equals.ts | 2 +- .../test/test-data/record/toJsonSchema.ts | 2 +- .../test/test-data/record/toString.ts | 2 +- .../test/test-data/record/validate.ts | 2 +- .../test/test-data/string/core.ts | 4 +- .../test/test-data/string/toJsonSchema.ts | 2 +- .../test/test-data/string/validate.ts | 2 +- .../test/test-data/symbol/validate.ts | 2 +- .../test/test-data/tuple/core.ts | 7 +- .../test/test-data/tuple/equals.ts | 2 +- .../test/test-data/tuple/toJsonSchema.ts | 2 +- .../test/test-data/tuple/toString.ts | 2 +- .../test/test-data/tuple/validate.ts | 2 +- .../test/test-data/undefined/validate.ts | 2 +- .../test/test-data/union/core.ts | 6 +- .../test/test-data/union/equals.ts | 2 +- .../test/test-data/union/toJsonSchema.ts | 2 +- .../test/test-data/union/toString.ts | 2 +- .../test/test-data/union/validate.ts | 2 +- .../test/test-data/unknown/validate.ts | 2 +- .../test/test-data/void/validate.ts | 2 +- packages/schema-generator/tsconfig.build.json | 2 +- packages/schema-generator/tsconfig.src.json | 2 +- packages/schema-generator/tsconfig.test.json | 2 +- packages/schema-seed/package.json | 4 +- .../src/__generated__/__manifest__.ts | 4 +- packages/schema-seed/src/arbitrary.ts | 2 +- packages/schema-seed/src/exports.ts | 2 +- packages/schema-seed/src/fast-check.ts | 21 - packages/schema-seed/src/seed.ts | 111 +- packages/schema-seed/test/seed.test.ts | 29 +- packages/schema-seed/tsconfig.build.json | 2 +- packages/schema-seed/tsconfig.src.json | 2 +- packages/schema-seed/tsconfig.test.json | 2 +- packages/schema-to-json-schema/package.json | 4 +- .../src/__generated__/__manifest__.ts | 4 +- packages/schema-to-json-schema/src/install.ts | 51 +- packages/schema-to-json-schema/src/items.ts | 3 +- .../schema-to-json-schema/src/jsonSchema.ts | 18 +- .../schema-to-json-schema/src/properties.ts | 21 +- .../schema-to-json-schema/src/prototypes.ts | 13 +- .../schema-to-json-schema/src/recursive.ts | 68 +- .../src/specification.ts | 2 +- .../test/install.test.ts | 4 +- .../test/jsonSchema.test.ts | 10 +- .../schema-to-json-schema/tsconfig.build.json | 2 +- .../schema-to-json-schema/tsconfig.src.json | 2 +- .../schema-to-json-schema/tsconfig.test.json | 2 +- packages/schema-to-string/package.json | 4 +- .../src/__generated__/__manifest__.ts | 4 +- packages/schema-to-string/src/install.ts | 22 +- packages/schema-to-string/src/recursive.ts | 3 +- packages/schema-to-string/src/shared.ts | 3 +- packages/schema-to-string/src/toString.ts | 17 +- .../schema-to-string/test/install.test.ts | 2 +- .../schema-to-string/test/integration.test.ts | 10 +- .../schema-to-string/test/toString.test.ts | 10 +- .../test/zod-side-by-side.test.ts | 4 +- packages/schema-to-string/tsconfig.build.json | 2 +- packages/schema-to-string/tsconfig.src.json | 2 +- packages/schema-to-string/tsconfig.test.json | 4 +- packages/schema/src/clone.ts | 19 - packages/schema/src/extensions.ts | 32 - packages/schema/src/namespace.ts | 80 -- packages/schema/test/combinators.test.ts | 43 - packages/schema/test/fast-check.ts | 92 -- pnpm-lock.yaml | 34 +- tsconfig.base.json | 44 +- tsconfig.build.json | 2 +- tsconfig.json | 2 +- vite.config.ts | 2 +- 248 files changed, 3033 insertions(+), 2454 deletions(-) delete mode 100755 bin/watch.ts delete mode 100644 packages/derive-codec/src/bind.ts rename packages/derive-validators/src/{prototype.ts => validate.ts} (65%) create mode 100644 packages/registry/src/predicate.ts rename packages/{schema => schema-core}/CHANGELOG.md (100%) rename packages/{schema => schema-core}/README.md (91%) rename packages/{schema => schema-core}/package.json (96%) rename packages/{schema => schema-core}/src/__generated__/__manifest__.ts (96%) rename packages/{schema/src/schema.ts => schema-core/src/__schema.ts__} (83%) create mode 100644 packages/schema-core/src/any.ts create mode 100644 packages/schema-core/src/array.ts create mode 100644 packages/schema-core/src/bigint.ts create mode 100644 packages/schema-core/src/boolean.ts rename packages/{schema => schema-core}/src/bounded.ts (79%) create mode 100644 packages/schema-core/src/clone.ts rename packages/{schema => schema-core}/src/combinators.ts (74%) create mode 100644 packages/schema-core/src/core.ts rename packages/{schema => schema-core}/src/enum.ts (90%) create mode 100644 packages/schema-core/src/eq.ts rename packages/{schema => schema-core}/src/equals.ts (100%) rename packages/{schema => schema-core}/src/exports.ts (93%) create mode 100644 packages/schema-core/src/extensions.ts rename packages/{schema => schema-core}/src/has.ts (59%) rename packages/{schema => schema-core}/src/index.ts (100%) create mode 100644 packages/schema-core/src/integer.ts create mode 100644 packages/schema-core/src/intersect.ts create mode 100644 packages/schema-core/src/key.ts rename packages/{schema/src/types.ts => schema-core/src/label.ts} (95%) create mode 100644 packages/schema-core/src/namespace.ts create mode 100644 packages/schema-core/src/never.ts create mode 100644 packages/schema-core/src/nonnullable.ts create mode 100644 packages/schema-core/src/null.ts create mode 100644 packages/schema-core/src/number.ts create mode 100644 packages/schema-core/src/object.ts create mode 100644 packages/schema-core/src/of.ts create mode 100644 packages/schema-core/src/optional.ts rename packages/{schema => schema-core}/src/postinstall.ts (100%) rename packages/{schema => schema-core}/src/predicates.ts (92%) create mode 100644 packages/schema-core/src/readonlyArray.ts create mode 100644 packages/schema-core/src/record.ts rename packages/{schema => schema-core}/src/recursive.ts (95%) create mode 100644 packages/schema-core/src/string.ts create mode 100644 packages/schema-core/src/symbol.ts create mode 100644 packages/schema-core/src/tuple.ts create mode 100644 packages/schema-core/src/types.ts create mode 100644 packages/schema-core/src/undefined.ts create mode 100644 packages/schema-core/src/union.ts create mode 100644 packages/schema-core/src/unknown.ts rename packages/{schema => schema-core}/src/utils.ts (91%) rename packages/{schema => schema-core}/src/version.ts (100%) create mode 100644 packages/schema-core/src/void.ts rename packages/{schema => schema-core}/test/bounded.test.ts (99%) create mode 100644 packages/schema-core/test/combinators.test.ts rename packages/{schema => schema-core}/test/enum.test.ts (57%) rename packages/{schema => schema-core}/test/equals.bench.ts (99%) rename packages/{schema => schema-core}/test/equals.test.ts (87%) rename packages/{schema => schema-core}/test/has.test.ts (72%) rename packages/{schema => schema-core}/test/inline.test.ts (97%) create mode 100644 packages/schema-core/test/intersect.test.ts create mode 100644 packages/schema-core/test/nonnullable.test.ts create mode 100644 packages/schema-core/test/optional.test.ts rename packages/{schema => schema-core}/test/predicates.test.ts (99%) rename packages/{schema => schema-core}/test/recursive.test.ts (64%) rename packages/{schema => schema-core}/test/schema.test.ts (98%) rename packages/{schema => schema-core}/test/seed.ts (97%) rename packages/{schema => schema-core}/test/utils.test.ts (96%) rename packages/{schema => schema-core}/test/version.test.ts (64%) rename packages/{schema => schema-core}/tsconfig.build.json (100%) rename packages/{schema => schema-core}/tsconfig.json (100%) rename packages/{schema => schema-core}/tsconfig.src.json (100%) rename packages/{schema => schema-core}/tsconfig.test.json (100%) rename packages/{schema => schema-core}/vitest.config.ts (100%) rename packages/schema-generator/test/{e2e.test.ts => __e2e.test.ts__} (99%) delete mode 100644 packages/schema/src/clone.ts delete mode 100644 packages/schema/src/extensions.ts delete mode 100644 packages/schema/src/namespace.ts delete mode 100644 packages/schema/test/combinators.test.ts delete mode 100644 packages/schema/test/fast-check.ts diff --git a/README.md b/README.md index cbeffba3..28c3f35d 100644 --- a/README.md +++ b/README.md @@ -10,18 +10,18 @@

- NPM Version + NPM Version   TypeScript   Static Badge   - npm + npm  
- npm bundle size (scoped) + npm bundle size (scoped)   Static Badge   @@ -34,14 +34,14 @@   •   TypeScript Playground   •   - npm + npm


-`@traversable/schema` exploits a TypeScript feature called +`@traversable/schema-core` exploits a TypeScript feature called [inferred type predicates](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#inferred-type-predicates) to do what libaries like `zod` do, without the additional runtime overhead or abstraction. @@ -55,14 +55,14 @@ to do what libaries like `zod` do, without the additional runtime overhead or ab ## Requirements The only hard requirement is [TypeScript 5.5](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/). -Since the core primitive that `@traversable/schema` is built on top of is +Since the core primitive that `@traversable/schema-core` is built on top of is [inferred type predicates](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#inferred-type-predicates), we do not have plans to backport to previous versions. ## Quick start ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' declare let ex_01: unknown @@ -90,7 +90,7 @@ if (schema_01(ex_01)) { ## Features -`@traversable/schema` is modular by schema (like valibot), but takes it a step further by making its feature set opt-in by default. +`@traversable/schema-core` is modular by schema (like valibot), but takes it a step further by making its feature set opt-in by default. The ability to add features like this is a knock-on effect of traversable's extensible core. @@ -104,14 +104,14 @@ which no other schema library currently does (although please file an issue if t This is possible because the traversable schemas are themselves just type predicates with a few additional properties that allow them to also be used for reflection. -- **Instructions:** To use this feature, define a predicate inline and `@traversable/schema` will figure out the rest. +- **Instructions:** To use this feature, define a predicate inline and `@traversable/schema-core` will figure out the rest. #### Example You can play with this example in the TypeScript Playground. ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export let Classes = t.object({ promise: (v) => v instanceof Promise, @@ -195,7 +195,7 @@ type Shorthand = t.typeof Play with this example in the [TypeScript playground](https://tsplay.dev/NaBEPm). ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import '@traversable/derive-validators/install' // ↑↑ importing `@traversable/derive-validators/install` adds `.validate` to all schemas @@ -241,7 +241,7 @@ keys are printed at runtime might differ from the order they appear on the type- Play with this example in the [TypeScript playground](https://tsplay.dev/W49jew) ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import '@traversable/schema-to-string/install' // ↑↑ importing `@traversable/schema-to-string/install` adds the upgraded `.toString` method on all schemas @@ -284,7 +284,7 @@ Play with this example in the [TypeScript playground](https://tsplay.dev/NB98Vw) ```typescript import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import '@traversable/schema-to-json-schema/install' // ↑↑ importing `@traversable/schema-to-json-schema/install` adds `.toJsonSchema` on all schemas @@ -348,22 +348,21 @@ vi.assertType<{ ### Codec (`.pipe`, `.extend`, `.parse`, `.decode` & `.encode`) -- **Instructions:** to install the `.codec` method on all schemas, all you need to do is import `@traversable/derive-codec`. - - To create a covariant codec (similar to zod's `.transform`), use `.codec.pipe` - - To create a contravariant codec (similar to zod's `.preprocess`), use `.codec.extend` (WIP) +- **Instructions:** to install the `.pipe` and `.extend` methods on all schemas, simply `@traversable/derive-codec/install`. + - To create a covariant codec (similar to zod's `.transform`), use `.pipe` + - To create a contravariant codec (similar to zod's `.preprocess`), use `.extend` (WIP) #### Example Play with this example in the [TypeScript playground](https://tsplay.dev/mbbv3m). ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import '@traversable/derive-codec/install' -// ↑↑ importing `@traversable/derive-codec/install` adds `.codec` on all schemas +// ↑↑ importing `@traversable/derive-codec/install` adds `.pipe` and `.extend` on all schemas let User = t .object({ name: t.optional(t.string), createdAt: t.string }) - .codec // <-- notice we're pulling off the `.codec` property .pipe((user) => ({ ...user, createdAt: new Date(user.createdAt) })) .unpipe((user) => ({ ...user, createdAt: user.createdAt.toISOString() })) @@ -384,24 +383,24 @@ let toAPI = User.encode(fromAPI) flowchart TD registry(registry) json(json) -.-> registry(registry) - schema(schema) -.-> registry(registry) + schema-core(schema-core) -.-> registry(registry) derive-codec(derive-codec) -.-> registry(registry) - derive-codec(derive-codec) -.-> schema(schema) + derive-codec(derive-codec) -.-> schema-core(schema-core) derive-equals(derive-equals) -.-> json(json) derive-equals(derive-equals) -.-> registry(registry) - derive-equals(derive-equals) -.-> schema(schema) + derive-equals(derive-equals) -.-> schema-core(schema-core) derive-validators(derive-validators) -.-> json(json) derive-validators(derive-validators) -.-> registry(registry) - derive-validators(derive-validators) -.-> schema(schema) + derive-validators(derive-validators) -.-> schema-core(schema-core) schema-generator(schema-generator) -.-> registry(registry) - schema-generator(schema-generator) -.-> schema(schema) + schema-generator(schema-generator) -.-> schema-core(schema-core) schema-seed(schema-seed) -.-> json(json) schema-seed(schema-seed) -.-> registry(registry) - schema-seed(schema-seed) -.-> schema(schema) + schema-seed(schema-seed) -.-> schema-core(schema-core) schema-to-json-schema(schema-to-json-schema) -.-> registry(registry) - schema-to-json-schema(schema-to-json-schema) -.-> schema(schema) + schema-to-json-schema(schema-to-json-schema) -.-> schema-core(schema-core) schema-to-string(schema-to-string) -.-> registry(registry) - schema-to-string(schema-to-string) -.-> schema(schema) + schema-to-string(schema-to-string) -.-> schema-core(schema-core) schema-valibot-adapter(schema-valibot-adapter) -.-> json(json) schema-valibot-adapter(schema-valibot-adapter) -.-> registry(registry) schema-zod-adapter(schema-zod-adapter) -.-> json(json) diff --git a/bin/constants.ts b/bin/constants.ts index 10c38bae..60cadece 100644 --- a/bin/constants.ts +++ b/bin/constants.ts @@ -35,7 +35,7 @@ export let REG_EXP = { export let PATH = { readme: path.join(path.resolve(), 'README.md'), - schemaReadme: path.join(path.resolve(), 'packages', 'schema', 'README.md'), + schemaReadme: path.join(path.resolve(), 'packages', 'schema-core', 'README.md'), generated: path.join(path.resolve(), 'config', '__generated__'), generated_repo_metadata: path.join(path.resolve(), 'config', '__generated__', 'repo.json'), generated_package_list: path.join(path.resolve(), 'config', '__generated__', 'package-list.ts'), diff --git a/bin/docs.ts b/bin/docs.ts index d93d9c9f..17e54faa 100644 --- a/bin/docs.ts +++ b/bin/docs.ts @@ -53,9 +53,9 @@ let writeChangelogs: (list: string) => SideEffect = flow( * * ``` * [ - * { name: '@traversable/data', dependencies: [], order: 0 }, - * { name: '@traversable/core', dependencies: ['@traversable/data'], order: 1 }, - * { name: '@traversable/node', dependencies: ['@traversable/core', '@traversable/data'], order: 2 } + * { name: '@traversable/registry', dependencies: [], order: 0 }, + * { name: '@traversable/json', dependencies: ['@traversable/registry'], order: 1 }, + * { name: '@traversable/schema-core', dependencies: ['@traversable/registry', '@traversable/json'], order: 2 } * ] * ``` * @@ -63,9 +63,9 @@ let writeChangelogs: (list: string) => SideEffect = flow( * * ``` * flowchart TD - * core(@traversable/core) --> data(@traversable/data) - * node(@traversable/node) --> core(@traversable/core) - * node(@traversable/node) --> data(@traversable/data) + * core(@traversable/schema-core) --> data(@traversable/registry) + * node(@traversable/schema-core) --> core(@traversable/json) + * node(@traversable/json) --> data(@traversable/registry) * ``` * * The `README.md` file contains a block that looks like this: diff --git a/bin/process.ts b/bin/process.ts index 9a0417b2..43abfd26 100644 --- a/bin/process.ts +++ b/bin/process.ts @@ -1,4 +1,4 @@ -import * as cp from "node:child_process" +import * as ChildProcess from "node:child_process" import type { ShellOptions } from "./types.js" /** @@ -6,10 +6,10 @@ import type { ShellOptions } from "./types.js" * * Runs a command synchronously. Output goes to terminal. */ -export const $ +export const $ : (cmd: string, options?: ShellOptions) => void = (cmd: string, { env, ...rest } = {}) => { - cp.execSync(cmd, { + ChildProcess.execSync(cmd, { env: { ...process.env, ...env }, ...rest, stdio: "inherit", @@ -21,12 +21,12 @@ export const $ * * Runs a command synchronously. Output returned as a string. */ -export const shell +export const shell : (cmd: string, options?: ShellOptions) => string - = (cmd, { env, ...rest } = {}) => cp.execSync( - cmd, { - env: { ...process.env, ...env }, - ...rest, - stdio: "pipe" - } + = (cmd, { env, ...rest } = {}) => ChildProcess.execSync( + cmd, { + env: { ...process.env, ...env }, + ...rest, + stdio: "pipe" + } ).toString("utf8") diff --git a/bin/watch.ts b/bin/watch.ts deleted file mode 100755 index e63c2743..00000000 --- a/bin/watch.ts +++ /dev/null @@ -1,1061 +0,0 @@ -#!/usr/bin/env pnpm dlx tsx -import chokidar from "chokidar" -import * as path from "node:path" - -const logging = (_?: unknown) => ({ - ...globalThis.console, -}) - -// const debounce = -function debounce(handler: () => Promise, ms?: number): (_file: string) => void -function debounce(handler: () => Promise, ms: number = 10) { - let timeout: ReturnType | undefined - return () => { - if (timeout) globalThis.clearTimeout(timeout) - timeout = globalThis.setTimeout(handler, ms) - } -} - -const PATH = { - dir: path.join(path.resolve(), "packages", "algebra", "__schemas__"), -} as const - -function watch(root: string) { - const configPath = "path" - const config = { - dir: PATH.dir - } - // resolveConfigPath({ configDirectory: root, }) - const configWatcher = chokidar.watch(configPath) - - let watcher = new chokidar.FSWatcher({}) - - const generatorWatcher = () => { - // const config = getConfig() - - watcher.close() - - console.info(`TSR: Watching schemas in (${config.dir})...`) - watcher = chokidar.watch(config.dir) - - watcher.on('ready', async () => { - const handle = async () => { - try { - await generator(config, root) - } catch (err) { - console.error(err) - console.info() - } - } - - await handle() - - let timeout: ReturnType | undefined - - const deduped = (_file: string) => { - if (timeout) { - clearTimeout(timeout) - } - - timeout = setTimeout(handle, 10) - } - - watcher.on('change', deduped) - watcher.on('add', deduped) - watcher.on('unlink', deduped) - }) - } - - configWatcher.on('ready', generatorWatcher) - configWatcher.on('change', generatorWatcher) -} - -async function generator(config: {}, root: string): Promise { - console.log("calling generator") - return Promise.resolve("42") -} - -watch("root") - -// export async function generator(config: Config, root: string) { -// const logger = logging({ disabled: config.disableLogging }) -// logger.log('') - -// if (!isFirst) { -// logger.log('♻️ Generating routes...') -// isFirst = true -// } else if (skipMessage) { -// skipMessage = false -// } else { -// logger.log('♻️ Regenerating routes...') -// } - -// const taskId = latestTask + 1 -// latestTask = taskId - -// const checkLatest = () => { -// if (latestTask !== taskId) { -// skipMessage = true -// return false -// } - -// return true -// } - -// const start = Date.now() - -// const TYPES_DISABLED = config.disableTypes - -// const prettierOptions: prettier.Options = { -// semi: config.semicolons, -// singleQuote: config.quoteStyle === 'single', -// parser: 'typescript', -// } - -// let getRouteNodesResult: GetRouteNodesResult - -// if (config.virtualRouteConfig) { -// getRouteNodesResult = await virtualGetRouteNodes(config, root) -// } else { -// getRouteNodesResult = await physicalGetRouteNodes(config, root) -// } - -// const { rootRouteNode, routeNodes: beforeRouteNodes } = getRouteNodesResult -// if (rootRouteNode === undefined) { -// let errorMessage = `rootRouteNode must not be undefined. Make sure you've added your root route into the route-tree.` -// if (!config.virtualRouteConfig) { -// errorMessage += `\nMake sure that you add a "${rootPathId}.${config.disableTypes ? 'js' : 'tsx'}" file to your routes directory.\nAdd the file in: "${config.routesDirectory}/${rootPathId}.${config.disableTypes ? 'js' : 'tsx'}"` -// } -// throw new Error(errorMessage) -// } - -// const preRouteNodes = multiSortBy(beforeRouteNodes, [ -// (d) => (d.routePath === '/' ? -1 : 1), -// (d) => d.routePath?.split('/').length, -// (d) => -// d.filePath.match(new RegExp(`[./]${config.indexToken}[.]`)) ? 1 : -1, -// (d) => -// d.filePath.match( -// /[./](component|errorComponent|pendingComponent|loader|lazy)[.]/, -// ) -// ? 1 -// : -1, -// (d) => -// d.filePath.match(new RegExp(`[./]${config.routeToken}[.]`)) ? -1 : 1, -// (d) => (d.routePath?.endsWith('/') ? -1 : 1), -// (d) => d.routePath, -// ]).filter((d) => ![`/${rootPathId}`].includes(d.routePath || '')) - -// const routeTree: Array = [] -// const routePiecesByPath: Record = {} - -// // Loop over the flat list of routeNodes and -// // build up a tree based on the routeNodes' routePath -// const routeNodes: Array = [] - -// // the handleRootNode function is not being collapsed into the handleNode function -// // because it requires only a subset of the logic that the handleNode function requires -// // and it's easier to read and maintain this way -// const handleRootNode = async (node?: RouteNode) => { -// if (!node) { -// // currently this is not being handled, but it could be in the future -// // for example to handle a virtual root route -// return -// } - -// // from here on, we are only handling the root node that's present in the file system -// const routeCode = fs.readFileSync(node.fullPath, 'utf-8') - -// if (!routeCode) { -// const replaced = fillTemplate( -// [ -// 'import * as React from "react"\n', -// '%%tsrImports%%', -// '\n\n', -// '%%tsrExportStart%%{\n component: RootComponent\n }%%tsrExportEnd%%\n\n', -// 'function RootComponent() { return (
Hello "%%tsrPath%%"!
) };\n', -// ].join(''), -// { -// tsrImports: -// "import { Outlet, createRootRoute } from '@tanstack/react-router';", -// tsrPath: rootPathId, -// tsrExportStart: `export const Route = createRootRoute(`, -// tsrExportEnd: ');', -// }, -// ) - -// logger.log(`🟡 Creating ${node.fullPath}`) -// fs.writeFileSync( -// node.fullPath, -// await prettier.format(replaced, prettierOptions), -// ) -// } -// } - -// await handleRootNode(rootRouteNode) - -// const handleNode = async (node: RouteNode) => { -// let parentRoute = hasParentRoute(routeNodes, node, node.routePath) - -// // if the parent route is a virtual parent route, we need to find the real parent route -// if (parentRoute?.isVirtualParentRoute && parentRoute.children?.length) { -// // only if this sub-parent route returns a valid parent route, we use it, if not leave it as it -// const possibleParentRoute = hasParentRoute( -// parentRoute.children, -// node, -// node.routePath, -// ) -// if (possibleParentRoute) { -// parentRoute = possibleParentRoute -// } -// } - -// if (parentRoute) node.parent = parentRoute - -// node.path = determineNodePath(node) - -// const trimmedPath = trimPathLeft(node.path ?? '') - -// const split = trimmedPath.split('/') -// const lastRouteSegment = split[split.length - 1] ?? trimmedPath - -// node.isNonPath = -// lastRouteSegment.startsWith('_') || -// routeGroupPatternRegex.test(lastRouteSegment) - -// node.cleanedPath = removeGroups( -// removeUnderscores(removeLayoutSegments(node.path)) ?? '', -// ) - -// // Ensure the boilerplate for the route exists, which can be skipped for virtual parent routes and virtual routes -// if (!node.isVirtualParentRoute && !node.isVirtual) { -// const routeCode = fs.readFileSync(node.fullPath, 'utf-8') - -// const escapedRoutePath = node.routePath?.replaceAll('$', '$$') ?? '' - -// let replaced = routeCode - -// if (!routeCode) { -// if (node.isLazy) { -// replaced = fillTemplate(config.customScaffolding.routeTemplate, { -// tsrImports: -// "import { createLazyFileRoute } from '@tanstack/react-router';", -// tsrPath: escapedRoutePath, -// tsrExportStart: `export const Route = createLazyFileRoute('${escapedRoutePath}')(`, -// tsrExportEnd: ');', -// }) -// } else if ( -// node.isRoute || -// (!node.isComponent && -// !node.isErrorComponent && -// !node.isPendingComponent && -// !node.isLoader) -// ) { -// replaced = fillTemplate(config.customScaffolding.routeTemplate, { -// tsrImports: -// "import { createFileRoute } from '@tanstack/react-router';", -// tsrPath: escapedRoutePath, -// tsrExportStart: `export const Route = createFileRoute('${escapedRoutePath}')(`, -// tsrExportEnd: ');', -// }) -// } -// } else { -// replaced = routeCode -// .replace( -// /(FileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g, -// (_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`, -// ) -// .replace( -// /(import\s*\{.*)(create(Lazy)?FileRoute)(.*\}\s*from\s*['"]@tanstack\/react-router['"])/gs, -// (_, p1, __, ___, p4) => -// `${p1}${node.isLazy ? 'createLazyFileRoute' : 'createFileRoute'}${p4}`, -// ) -// .replace( -// /create(Lazy)?FileRoute(\(\s*['"])([^\s]*)(['"],?\s*\))/g, -// (_, __, p2, ___, p4) => -// `${node.isLazy ? 'createLazyFileRoute' : 'createFileRoute'}${p2}${escapedRoutePath}${p4}`, -// ) -// } - -// await writeIfDifferent( -// node.fullPath, -// prettierOptions, -// routeCode, -// replaced, -// { -// beforeWrite: () => { -// logger.log(`🟡 Updating ${node.fullPath}`) -// }, -// }, -// ) -// } - -// if ( -// !node.isVirtual && -// (node.isLoader || -// node.isComponent || -// node.isErrorComponent || -// node.isPendingComponent || -// node.isLazy) -// ) { -// routePiecesByPath[node.routePath!] = -// routePiecesByPath[node.routePath!] || {} - -// routePiecesByPath[node.routePath!]![ -// node.isLazy -// ? 'lazy' -// : node.isLoader -// ? 'loader' -// : node.isErrorComponent -// ? 'errorComponent' -// : node.isPendingComponent -// ? 'pendingComponent' -// : 'component' -// ] = node - -// const anchorRoute = routeNodes.find((d) => d.routePath === node.routePath) - -// if (!anchorRoute) { -// await handleNode({ -// ...node, -// isVirtual: true, -// isLazy: false, -// isLoader: false, -// isComponent: false, -// isErrorComponent: false, -// isPendingComponent: false, -// }) -// } -// return -// } - -// const cleanedPathIsEmpty = (node.cleanedPath || '').length === 0 -// const nonPathRoute = node.isRoute && node.isNonPath -// node.isVirtualParentRequired = -// node.isLayout || nonPathRoute ? !cleanedPathIsEmpty : false -// if (!node.isVirtual && node.isVirtualParentRequired) { -// const parentRoutePath = removeLastSegmentFromPath(node.routePath) || '/' -// const parentVariableName = routePathToVariable(parentRoutePath) - -// const anchorRoute = routeNodes.find( -// (d) => d.routePath === parentRoutePath, -// ) - -// if (!anchorRoute) { -// const parentNode = { -// ...node, -// path: removeLastSegmentFromPath(node.path) || '/', -// filePath: removeLastSegmentFromPath(node.filePath) || '/', -// fullPath: removeLastSegmentFromPath(node.fullPath) || '/', -// routePath: parentRoutePath, -// variableName: parentVariableName, -// isVirtual: true, -// isLayout: false, -// isVirtualParentRoute: true, -// isVirtualParentRequired: false, -// } - -// parentNode.children = parentNode.children ?? [] -// parentNode.children.push(node) - -// node.parent = parentNode - -// if (node.isLayout) { -// // since `node.path` is used as the `id` on the route definition, we need to update it -// node.path = determineNodePath(node) -// } - -// await handleNode(parentNode) -// } else { -// anchorRoute.children = anchorRoute.children ?? [] -// anchorRoute.children.push(node) - -// node.parent = anchorRoute -// } -// } - -// if (node.parent) { -// if (!node.isVirtualParentRequired) { -// node.parent.children = node.parent.children ?? [] -// node.parent.children.push(node) -// } -// } else { -// routeTree.push(node) -// } - -// routeNodes.push(node) -// } - -// for (const node of preRouteNodes.filter((d) => !d.isAPIRoute)) { -// await handleNode(node) -// } -// checkRouteFullPathUniqueness( -// preRouteNodes.filter( -// (d) => !d.isAPIRoute && d.children === undefined && d.isLazy !== true, -// ), -// config, -// ) - -// const startAPIRouteNodes: Array = checkStartAPIRoutes( -// preRouteNodes.filter((d) => d.isAPIRoute), -// config, -// ) - -// const handleAPINode = async (node: RouteNode) => { -// const routeCode = fs.readFileSync(node.fullPath, 'utf-8') - -// const escapedRoutePath = node.routePath?.replaceAll('$', '$$') ?? '' - -// if (!routeCode) { -// const replaced = fillTemplate(config.customScaffolding.apiTemplate, { -// tsrImports: "import { createAPIFileRoute } from '@tanstack/start/api';", -// tsrPath: escapedRoutePath, -// tsrExportStart: `export const ${CONSTANTS.APIRouteExportVariable} = createAPIFileRoute('${escapedRoutePath}')(`, -// tsrExportEnd: ');', -// }) - -// logger.log(`🟡 Creating ${node.fullPath}`) -// fs.writeFileSync( -// node.fullPath, -// await prettier.format(replaced, prettierOptions), -// ) -// } else { -// await writeIfDifferent( -// node.fullPath, -// prettierOptions, -// routeCode, -// routeCode.replace( -// /(createAPIFileRoute\(\s*['"])([^\s]*)(['"],?\s*\))/g, -// (_, p1, __, p3) => `${p1}${escapedRoutePath}${p3}`, -// ), -// { -// beforeWrite: () => { -// logger.log(`🟡 Updating ${node.fullPath}`) -// }, -// }, -// ) -// } -// } - -// for (const node of startAPIRouteNodes) { -// await handleAPINode(node) -// } - -// function buildRouteTreeConfig(nodes: Array, depth = 1): string { -// const children = nodes.map((node) => { -// if (node.isRoot) { -// return -// } - -// if (node.isLayout && !node.children?.length) { -// return -// } - -// const route = `${node.variableName}Route` - -// if (node.children?.length) { -// const childConfigs = buildRouteTreeConfig(node.children, depth + 1) - -// const childrenDeclaration = TYPES_DISABLED -// ? '' -// : `interface ${route}Children { -// ${node.children.map((child) => `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`).join(',')} -// }` - -// const children = `const ${route}Children${TYPES_DISABLED ? '' : `: ${route}Children`} = { -// ${node.children.map((child) => `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`).join(',')} -// }` - -// const routeWithChildren = `const ${route}WithChildren = ${route}._addFileChildren(${route}Children)` - -// return [ -// childConfigs, -// childrenDeclaration, -// children, -// routeWithChildren, -// ].join('\n\n') -// } - -// return undefined -// }) - -// return children.filter(Boolean).join('\n\n') -// } - -// const routeConfigChildrenText = buildRouteTreeConfig(routeTree) - -// const sortedRouteNodes = multiSortBy(routeNodes, [ -// (d) => (d.routePath?.includes(`/${rootPathId}`) ? -1 : 1), -// (d) => d.routePath?.split('/').length, -// (d) => (d.routePath?.endsWith(config.indexToken) ? -1 : 1), -// (d) => d, -// ]) - -// const imports = Object.entries({ -// createFileRoute: sortedRouteNodes.some((d) => d.isVirtual), -// lazyFn: sortedRouteNodes.some( -// (node) => routePiecesByPath[node.routePath!]?.loader, -// ), -// lazyRouteComponent: sortedRouteNodes.some( -// (node) => -// routePiecesByPath[node.routePath!]?.component || -// routePiecesByPath[node.routePath!]?.errorComponent || -// routePiecesByPath[node.routePath!]?.pendingComponent, -// ), -// }) -// .filter((d) => d[1]) -// .map((d) => d[0]) - -// const virtualRouteNodes = sortedRouteNodes.filter((d) => d.isVirtual) - -// function getImportPath(node: RouteNode) { -// return replaceBackslash( -// removeExt( -// path.relative( -// path.dirname(config.generatedRouteTree), -// path.resolve(config.routesDirectory, node.filePath), -// ), -// config.addExtensions, -// ), -// ) -// } -// const routeImports = [ -// ...config.routeTreeFileHeader, -// `// This file was automatically generated by TanStack Router. -// // You should NOT make any changes in this file as it will be overwritten. -// // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.`, -// imports.length -// ? `import { ${imports.join(', ')} } from '@tanstack/react-router'\n` -// : '', -// '// Import Routes', -// [ -// `import { Route as rootRoute } from './${getImportPath(rootRouteNode)}'`, -// ...sortedRouteNodes -// .filter((d) => !d.isVirtual) -// .map((node) => { -// return `import { Route as ${ -// node.variableName -// }Import } from './${getImportPath(node)}'` -// }), -// ].join('\n'), -// virtualRouteNodes.length ? '// Create Virtual Routes' : '', -// virtualRouteNodes -// .map((node) => { -// return `const ${ -// node.variableName -// }Import = createFileRoute('${node.routePath}')()` -// }) -// .join('\n'), -// '// Create/Update Routes', -// sortedRouteNodes -// .map((node) => { -// const loaderNode = routePiecesByPath[node.routePath!]?.loader -// const componentNode = routePiecesByPath[node.routePath!]?.component -// const errorComponentNode = -// routePiecesByPath[node.routePath!]?.errorComponent -// const pendingComponentNode = -// routePiecesByPath[node.routePath!]?.pendingComponent -// const lazyComponentNode = routePiecesByPath[node.routePath!]?.lazy - -// return [ -// `const ${node.variableName}Route = ${node.variableName}Import.update({ -// ${[ -// `id: '${node.path}'`, -// !node.isNonPath ? `path: '${node.cleanedPath}'` : undefined, -// `getParentRoute: () => ${node.parent?.variableName ?? 'root'}Route`, -// ] -// .filter(Boolean) -// .join(',')} -// }${TYPES_DISABLED ? '' : 'as any'})`, -// loaderNode -// ? `.updateLoader({ loader: lazyFn(() => import('./${replaceBackslash( -// removeExt( -// path.relative( -// path.dirname(config.generatedRouteTree), -// path.resolve(config.routesDirectory, loaderNode.filePath), -// ), -// config.addExtensions, -// ), -// )}'), 'loader') })` -// : '', -// componentNode || errorComponentNode || pendingComponentNode -// ? `.update({ -// ${( -// [ -// ['component', componentNode], -// ['errorComponent', errorComponentNode], -// ['pendingComponent', pendingComponentNode], -// ] as const -// ) -// .filter((d) => d[1]) -// .map((d) => { -// return `${ -// d[0] -// }: lazyRouteComponent(() => import('./${replaceBackslash( -// removeExt( -// path.relative( -// path.dirname(config.generatedRouteTree), -// path.resolve(config.routesDirectory, d[1]!.filePath), -// ), -// config.addExtensions, -// ), -// )}'), '${d[0]}')` -// }) -// .join('\n,')} -// })` -// : '', -// lazyComponentNode -// ? `.lazy(() => import('./${replaceBackslash( -// removeExt( -// path.relative( -// path.dirname(config.generatedRouteTree), -// path.resolve( -// config.routesDirectory, -// lazyComponentNode.filePath, -// ), -// ), -// config.addExtensions, -// ), -// )}').then((d) => d.Route))` -// : '', -// ].join('') -// }) -// .join('\n\n'), -// ...(TYPES_DISABLED -// ? [] -// : [ -// '// Populate the FileRoutesByPath interface', -// `declare module '@tanstack/react-router' { -// interface FileRoutesByPath { -// ${routeNodes -// .map((routeNode) => { -// const filePathId = routeNode.routePath - -// return `'${filePathId}': { -// id: '${filePathId}' -// path: '${inferPath(routeNode)}' -// fullPath: '${inferFullPath(routeNode)}' -// preLoaderRoute: typeof ${routeNode.variableName}Import -// parentRoute: typeof ${ -// routeNode.isVirtualParentRequired -// ? `${routeNode.parent?.variableName}Route` -// : routeNode.parent?.variableName -// ? `${routeNode.parent.variableName}Import` -// : 'rootRoute' -// } -// }` -// }) -// .join('\n')} -// } -// }`, -// ]), -// '// Create and export the route tree', -// routeConfigChildrenText, -// ...(TYPES_DISABLED -// ? [] -// : [ -// `export interface FileRoutesByFullPath { -// ${[...createRouteNodesByFullPath(routeNodes).entries()].map( -// ([fullPath, routeNode]) => { -// return `'${fullPath}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` -// }, -// )} -// }`, -// `export interface FileRoutesByTo { -// ${[...createRouteNodesByTo(routeNodes).entries()].map(([to, routeNode]) => { -// return `'${to}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` -// })} -// }`, -// `export interface FileRoutesById { -// '__root__': typeof rootRoute, -// ${[...createRouteNodesById(routeNodes).entries()].map(([id, routeNode]) => { -// return `'${id}': typeof ${getResolvedRouteNodeVariableName(routeNode)}` -// })} -// }`, -// `export interface FileRouteTypes { -// fileRoutesByFullPath: FileRoutesByFullPath -// fullPaths: ${routeNodes.length > 0 ? [...createRouteNodesByFullPath(routeNodes).keys()].map((fullPath) => `'${fullPath}'`).join('|') : 'never'} -// fileRoutesByTo: FileRoutesByTo -// to: ${routeNodes.length > 0 ? [...createRouteNodesByTo(routeNodes).keys()].map((to) => `'${to}'`).join('|') : 'never'} -// id: ${[`'__root__'`, ...[...createRouteNodesById(routeNodes).keys()].map((id) => `'${id}'`)].join('|')} -// fileRoutesById: FileRoutesById -// }`, -// `export interface RootRouteChildren { -// ${routeTree.map((child) => `${child.variableName}Route: typeof ${getResolvedRouteNodeVariableName(child)}`).join(',')} -// }`, -// ]), -// `const rootRouteChildren${TYPES_DISABLED ? '' : ': RootRouteChildren'} = { -// ${routeTree.map((child) => `${child.variableName}Route: ${getResolvedRouteNodeVariableName(child)}`).join(',')} -// }`, -// `export const routeTree = rootRoute._addFileChildren(rootRouteChildren)${TYPES_DISABLED ? '' : '._addFileTypes()'}`, -// ...config.routeTreeFileFooter, -// ] -// .filter(Boolean) -// .join('\n\n') - -// const createRouteManifest = () => { -// const routesManifest = { -// __root__: { -// filePath: rootRouteNode.filePath, -// children: routeTree.map((d) => d.routePath), -// }, -// ...Object.fromEntries( -// routeNodes.map((d) => { -// const filePathId = d.routePath - -// return [ -// filePathId, -// { -// filePath: d.filePath, -// parent: d.parent?.routePath ? d.parent.routePath : undefined, -// children: d.children?.map((childRoute) => childRoute.routePath), -// }, -// ] -// }), -// ), -// } - -// return JSON.stringify( -// { -// routes: routesManifest, -// }, -// null, -// 2, -// ) -// } - -// const routeConfigFileContent = config.disableManifestGeneration -// ? routeImports -// : [ -// routeImports, -// '\n', -// '/* ROUTE_MANIFEST_START', -// createRouteManifest(), -// 'ROUTE_MANIFEST_END */', -// ].join('\n') - -// if (!checkLatest()) return - -// const existingRouteTreeContent = await fsp -// .readFile(path.resolve(config.generatedRouteTree), 'utf-8') -// .catch((err) => { -// if (err.code === 'ENOENT') { -// return '' -// } - -// throw err -// }) - -// if (!checkLatest()) return - -// // Ensure the directory exists -// await fsp.mkdir(path.dirname(path.resolve(config.generatedRouteTree)), { -// recursive: true, -// }) - -// if (!checkLatest()) return - -// // Write the route tree file, if it has changed -// const routeTreeWriteResult = await writeIfDifferent( -// path.resolve(config.generatedRouteTree), -// prettierOptions, -// existingRouteTreeContent, -// routeConfigFileContent, -// { -// beforeWrite: () => { -// logger.log(`🟡 Updating ${config.generatedRouteTree}`) -// }, -// }, -// ) -// if (routeTreeWriteResult && !checkLatest()) { -// return -// } - -// logger.log( -// `✅ Processed ${routeNodes.length === 1 ? 'route' : 'routes'} in ${ -// Date.now() - start -// }ms`, -// ) -// } - -// function removeGroups(s: string) { -// return s.replace(possiblyNestedRouteGroupPatternRegex, '') -// } - -// /** -// * The `node.path` is used as the `id` in the route definition. -// * This function checks if the given node has a parent and if so, it determines the correct path for the given node. -// * @param node - The node to determine the path for. -// * @returns The correct path for the given node. -// */ -// function determineNodePath(node: RouteNode) { -// return (node.path = node.parent -// ? node.routePath?.replace(node.parent.routePath ?? '', '') || '/' -// : node.routePath) -// } - -// /** -// * Removes the last segment from a given path. Segments are considered to be separated by a '/'. -// * -// * @param {string} routePath - The path from which to remove the last segment. Defaults to '/'. -// * @returns {string} The path with the last segment removed. -// * @example -// * removeLastSegmentFromPath('/workspace/_auth/foo') // '/workspace/_auth' -// */ -// export function removeLastSegmentFromPath(routePath: string = '/'): string { -// const segments = routePath.split('/') -// segments.pop() // Remove the last segment -// return segments.join('/') -// } - -// /** -// * Removes all segments from a given path that start with an underscore ('_'). -// * -// * @param {string} routePath - The path from which to remove segments. Defaults to '/'. -// * @returns {string} The path with all underscore-prefixed segments removed. -// * @example -// * removeLayoutSegments('/workspace/_auth/foo') // '/workspace/foo' -// */ -// function removeLayoutSegments(routePath: string = '/'): string { -// const segments = routePath.split('/') -// const newSegments = segments.filter((segment) => !segment.startsWith('_')) -// return newSegments.join('/') -// } - -// export function hasParentRoute( -// routes: Array, -// node: RouteNode, -// routePathToCheck: string | undefined, -// ): RouteNode | null { -// if (!routePathToCheck || routePathToCheck === '/') { -// return null -// } - -// const sortedNodes = multiSortBy(routes, [ -// (d) => d.routePath!.length * -1, -// (d) => d.variableName, -// ]).filter((d) => d.routePath !== `/${rootPathId}`) - -// for (const route of sortedNodes) { -// if (route.routePath === '/') continue - -// if ( -// routePathToCheck.startsWith(`${route.routePath}/`) && -// route.routePath !== routePathToCheck -// ) { -// return route -// } -// } - -// const segments = routePathToCheck.split('/') -// segments.pop() // Remove the last segment -// const parentRoutePath = segments.join('/') - -// return hasParentRoute(routes, node, parentRoutePath) -// } - -// /** -// * Gets the final variable name for a route -// */ -// export const getResolvedRouteNodeVariableName = ( -// routeNode: RouteNode, -// ): string => { -// return routeNode.children?.length -// ? `${routeNode.variableName}RouteWithChildren` -// : `${routeNode.variableName}Route` -// } - -// /** -// * Creates a map from fullPath to routeNode -// */ -// export const createRouteNodesByFullPath = ( -// routeNodes: Array, -// ): Map => { -// return new Map( -// routeNodes.map((routeNode) => [inferFullPath(routeNode), routeNode]), -// ) -// } - -// /** -// * Create a map from 'to' to a routeNode -// */ -// export const createRouteNodesByTo = ( -// routeNodes: Array, -// ): Map => { -// return new Map( -// dedupeBranchesAndIndexRoutes(routeNodes).map((routeNode) => [ -// inferTo(routeNode), -// routeNode, -// ]), -// ) -// } - -// /** -// * Create a map from 'id' to a routeNode -// */ -// export const createRouteNodesById = ( -// routeNodes: Array, -// ): Map => { -// return new Map( -// routeNodes.map((routeNode) => { -// const id = routeNode.routePath ?? '' -// return [id, routeNode] -// }), -// ) -// } - -// /** -// * Infers the full path for use by TS -// */ -// export const inferFullPath = (routeNode: RouteNode): string => { -// const fullPath = removeGroups( -// removeUnderscores(removeLayoutSegments(routeNode.routePath)) ?? '', -// ) - -// return routeNode.cleanedPath === '/' ? fullPath : fullPath.replace(/\/$/, '') -// } - -// /** -// * Infers the path for use by TS -// */ -// export const inferPath = (routeNode: RouteNode): string => { -// return routeNode.cleanedPath === '/' -// ? routeNode.cleanedPath -// : (routeNode.cleanedPath?.replace(/\/$/, '') ?? '') -// } - -// /** -// * Infers to path -// */ -// export const inferTo = (routeNode: RouteNode): string => { -// const fullPath = inferFullPath(routeNode) - -// if (fullPath === '/') return fullPath - -// return fullPath.replace(/\/$/, '') -// } - -// /** -// * Dedupes branches and index routes -// */ -// export const dedupeBranchesAndIndexRoutes = ( -// routes: Array, -// ): Array => { -// return routes.filter((route) => { -// if (route.children?.find((child) => child.cleanedPath === '/')) return false -// return true -// }) -// } - -// function checkUnique(routes: Array, key: keyof TElement) { -// // Check no two routes have the same `key` -// // if they do, throw an error with the conflicting filePaths -// const keys = routes.map((d) => d[key]) -// const uniqueKeys = new Set(keys) -// if (keys.length !== uniqueKeys.size) { -// const duplicateKeys = keys.filter((d, i) => keys.indexOf(d) !== i) -// const conflictingFiles = routes.filter((d) => -// duplicateKeys.includes(d[key]), -// ) -// return conflictingFiles -// } -// return undefined -// } - -// function checkRouteFullPathUniqueness( -// _routes: Array, -// config: Config, -// ) { -// const routes = _routes.map((d) => { -// const inferredFullPath = inferFullPath(d) -// return { ...d, inferredFullPath } -// }) - -// const conflictingFiles = checkUnique(routes, 'inferredFullPath') - -// if (conflictingFiles !== undefined) { -// const errorMessage = `Conflicting configuration paths were found for the following route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles -// .map((p) => `"${p.inferredFullPath}"`) -// .join(', ')}. -// Please ensure each route has a unique full path. -// Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n` -// throw new Error(errorMessage) -// } -// } - -// function checkStartAPIRoutes(_routes: Array, config: Config) { -// if (_routes.length === 0) { -// return [] -// } - -// // Make sure these are valid URLs -// // Route Groups and Layout Routes aren't being removed since -// // you may want to have an API route that starts with an underscore -// // or be wrapped in parentheses -// const routes = _routes.map((d) => { -// const routePath = removeTrailingSlash(d.routePath ?? '') -// return { ...d, routePath } -// }) - -// const conflictingFiles = checkUnique(routes, 'routePath') - -// if (conflictingFiles !== undefined) { -// const errorMessage = `Conflicting configuration paths were found for the following API route${conflictingFiles.length > 1 ? 's' : ''}: ${conflictingFiles -// .map((p) => `"${p}"`) -// .join(', ')}. -// Please ensure each API route has a unique route path. -// Conflicting files: \n ${conflictingFiles.map((d) => path.resolve(config.routesDirectory, d.filePath)).join('\n ')}\n` -// throw new Error(errorMessage) -// } - -// return routes -// } - -// export type StartAPIRoutePathSegment = { -// value: string -// type: 'path' | 'param' | 'splat' -// } - -// /** -// * This function takes in a path in the format accepted by TanStack Router -// * and returns an array of path segments that can be used to generate -// * the pathname of the TanStack Start API route. -// * -// * @param src -// * @returns -// */ -// export function startAPIRouteSegmentsFromTSRFilePath( -// src: string, -// config: Config, -// ): Array { -// const routePath = determineInitialRoutePath(src) - -// const parts = routePath -// .replaceAll('.', '/') -// .split('/') -// .filter((p) => !!p && p !== config.indexToken) -// const segments: Array = parts.map((part) => { -// if (part.startsWith('$')) { -// if (part === '$') { -// return { value: part, type: 'splat' } -// } - -// part.replaceAll('$', '') -// return { value: part, type: 'param' } -// } - -// return { value: part, type: 'path' } -// }) - -// return segments -// } - -// type TemplateTag = 'tsrImports' | 'tsrPath' | 'tsrExportStart' | 'tsrExportEnd' - -// function fillTemplate(template: string, values: Record) { -// return template.replace( -// /%%(\w+)%%/g, -// (_, key) => values[key as TemplateTag] || '', -// ) -// } - diff --git a/config/__generated__/package-list.ts b/config/__generated__/package-list.ts index 3ec1989a..3a9d053b 100644 --- a/config/__generated__/package-list.ts +++ b/config/__generated__/package-list.ts @@ -4,7 +4,7 @@ export const PACKAGES = [ "packages/derive-validators", "packages/json", "packages/registry", - "packages/schema", + "packages/schema-core", "packages/schema-generator", "packages/schema-seed", "packages/schema-to-json-schema", diff --git a/examples/sandbox/package.json b/examples/sandbox/package.json index b745f7ce..04b67838 100644 --- a/examples/sandbox/package.json +++ b/examples/sandbox/package.json @@ -19,7 +19,7 @@ "@traversable/derive-validators": "workspace:^", "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^", "@traversable/schema-to-json-schema": "workspace:^", "@traversable/schema-to-string": "workspace:^", diff --git a/examples/sandbox/src/demo/advanced.tsx b/examples/sandbox/src/demo/advanced.tsx index 229af6f0..40257e46 100644 --- a/examples/sandbox/src/demo/advanced.tsx +++ b/examples/sandbox/src/demo/advanced.tsx @@ -9,7 +9,7 @@ import { spacemacs as theme } from '../lib/theme' /** * DEMO: advanced * - * How to take full advantage of the core primitive that `@traverable/schema` is + * How to take full advantage of the core primitive that `@traversable/schema-core` is * built on: recursion schemes. * * The point of recursion schemes is to "factor out recursion". diff --git a/examples/sandbox/src/demo/codec.ts b/examples/sandbox/src/demo/codec.ts index d41bdcd3..3ff55d9c 100644 --- a/examples/sandbox/src/demo/codec.ts +++ b/examples/sandbox/src/demo/codec.ts @@ -3,16 +3,15 @@ import { t } from '../lib' /** * DEMO: converting your schema into a bi-directional codec * - * Import from `@traversable/derive-codec` to install the '.codec' method to all schemas. + * Import from `@traversable/derive-codec` to install the `.pipe` and `.extend` methods to all schemas. * - * From there, you'll have access to `.pipe`, `.extend`, `.decode` and `.encode`. You can pipe and extend - * as many times as you want -- the transformations will be added to a queue (`.pipe` puts a transformation - * in the "after" queue, `.extend` puts a preprocessor in the "before" queue). + * You can pipe and extend as many times as you want -- the transformations will be added to a queue + * (`.pipe` puts a transformation in the "after" queue, `.extend` puts a preprocessor in the "before" queue). * * If you need to recover the original schema, you can access it via the `.schema` property on the codec. */ let User = t - .object({ name: t.optional(t.string), createdAt: t.string }).codec + .object({ name: t.optional(t.string), createdAt: t.string }) .pipe(({ name = '', ...user }) => ({ ...user, firstName: name.split(' ')[0], lastName: name.split(' ')[1] ?? '', createdAt: new Date(user.createdAt) })) .unpipe(({ firstName, lastName, ...user }) => ({ ...user, name: firstName + ' ' + lastName, createdAt: user.createdAt.toISOString() })) diff --git a/examples/sandbox/src/demo/inferredTypePredicates.ts b/examples/sandbox/src/demo/inferredTypePredicates.ts index a8e58a6e..0c5923d1 100644 --- a/examples/sandbox/src/demo/inferredTypePredicates.ts +++ b/examples/sandbox/src/demo/inferredTypePredicates.ts @@ -23,7 +23,7 @@ export let classes = t.object({ /** * DEMO: inferred type predicates * - * You can write inline type predicates, and `@traversable/schema` keep track of + * You can write inline type predicates, and `@traversable/schema-core` keep track of * that at the type-level, even when the predicate is used inside a larger schema: */ export let values = t.object({ diff --git a/examples/sandbox/src/global.d.ts b/examples/sandbox/src/global.d.ts index bede536d..52081912 100644 --- a/examples/sandbox/src/global.d.ts +++ b/examples/sandbox/src/global.d.ts @@ -1,4 +1,4 @@ -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' declare global { var t: typeof t } diff --git a/examples/sandbox/src/lib/functor.tsx b/examples/sandbox/src/lib/functor.tsx index 39ee8ed5..831bf320 100644 --- a/examples/sandbox/src/lib/functor.tsx +++ b/examples/sandbox/src/lib/functor.tsx @@ -1,8 +1,8 @@ import type * as T from '@traversable/registry' import { fn } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' -import type { Free, Fixpoint } from './shared' +import type { Free } from './shared' import { MapSymbol, SetSymbol, URI } from './shared' import { set as Set } from './set' @@ -16,18 +16,17 @@ let map: T.Functor['map'] = (f) => (x) => { } } -export const Functor: T.Functor.Ix = { +export const Functor: T.Functor.Ix = { map, mapWithIndex(f) { return (x, ix) => { switch (true) { - default: return fn.exhaustive(x) case x.tag === URI.set: return Set(f(x.def, [...ix, SetSymbol])) case x.tag === URI.map: return Map(f(x.def[0], [...ix, MapSymbol, 0]), f(x.def[1], [...ix, MapSymbol, 1])) - case t.isCore(x): return t.IndexedFunctor.mapWithIndex(f)(x, ix) + default: return t.IndexedFunctor.mapWithIndex(f)(x, ix) } } - }, + } } export const fold = fn.cataIx(Functor) diff --git a/examples/sandbox/src/lib/index.ts b/examples/sandbox/src/lib/index.ts index dafb2bdc..edccf6be 100644 --- a/examples/sandbox/src/lib/index.ts +++ b/examples/sandbox/src/lib/index.ts @@ -1,34 +1,34 @@ export * as t from './namespace' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import '@traversable/derive-codec/install' import '@traversable/derive-equals/install' import '@traversable/derive-validators/install' import '@traversable/schema-to-json-schema/install' import '@traversable/schema-to-string/install' -import { prototype } from './prototype' +import { bindParse } from './prototype' export function bind() { - Object.assign(t.never, prototype) - Object.assign(t.unknown, prototype) - Object.assign(t.void, prototype) - Object.assign(t.null, prototype) - Object.assign(t.undefined, prototype) - Object.assign(t.boolean, prototype) - Object.assign(t.symbol, prototype) - Object.assign(t.integer, prototype) - Object.assign(t.bigint, prototype) - Object.assign(t.number, prototype) - Object.assign(t.string, prototype) - Object.assign(t.eq.prototype, prototype) - Object.assign(t.optional.prototype, prototype) - Object.assign(t.array.prototype, prototype) - Object.assign(t.record.prototype, prototype) - Object.assign(t.union.prototype, prototype) - Object.assign(t.intersect.prototype, prototype) - Object.assign(t.tuple.prototype, prototype) - Object.assign(t.object.prototype, prototype) - Object.assign(t.enum.prototype, prototype) + Object.assign(t.never, bindParse) + Object.assign(t.unknown, bindParse) + Object.assign(t.void, bindParse) + Object.assign(t.null, bindParse) + Object.assign(t.undefined, bindParse) + Object.assign(t.boolean, bindParse) + Object.assign(t.symbol, bindParse) + Object.assign(t.integer, bindParse) + Object.assign(t.bigint, bindParse) + Object.assign(t.number, bindParse) + Object.assign(t.string, bindParse) + Object.assign(t.eq.userDefinitions, bindParse) + Object.assign(t.optional.userDefinitions, bindParse) + Object.assign(t.array.userDefinitions, bindParse) + Object.assign(t.record.userDefinitions, bindParse) + Object.assign(t.union.userDefinitions, bindParse) + Object.assign(t.intersect.userDefinitions, bindParse) + Object.assign(t.tuple.userDefinitions, bindParse) + Object.assign(t.object.userDefinitions, bindParse) + Object.assign(t.enum.userDefinitions, bindParse) } @@ -39,8 +39,8 @@ export interface parse { parse(u: this['_type' & keyof this] | {} | null | undefined): this['_type' & keyof this] } -declare module '@traversable/schema' { - interface t_never extends parse { } +declare module '@traversable/schema-core' { + // interface t_never extends parse { } interface t_unknown extends parse { } interface t_any extends parse { } interface t_void extends parse { } diff --git a/examples/sandbox/src/lib/map.ts b/examples/sandbox/src/lib/map.ts index 2314a61c..de5981af 100644 --- a/examples/sandbox/src/lib/map.ts +++ b/examples/sandbox/src/lib/map.ts @@ -1,5 +1,5 @@ import * as T from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import type { ValidationError, Validate, @@ -53,8 +53,8 @@ export namespace map { export type type = never | Map export function def(k: K, v: V): map { type T = Map - let keyPredicate = t.isPredicate(k) ? k : (_?: any) => true - let valuePredicate = t.isPredicate(v) ? v : (_?: any) => true + let keyPredicate = T.isPredicate(k) ? k : (_?: any) => true + let valuePredicate = T.isPredicate(v) ? v : (_?: any) => true function MapSchema(u: map['_type'] | {} | null | undefined): u is T { if (!(u instanceof globalThis.Map)) return false else { diff --git a/examples/sandbox/src/lib/namespace.ts b/examples/sandbox/src/lib/namespace.ts index a627dd4c..8e779d2c 100644 --- a/examples/sandbox/src/lib/namespace.ts +++ b/examples/sandbox/src/lib/namespace.ts @@ -1,5 +1,5 @@ -export * from '@traversable/schema/namespace' -export { getConfig } from '@traversable/schema' +export * from '@traversable/schema-core/namespace' +export { getConfig } from '@traversable/schema-core' export type { F, diff --git a/examples/sandbox/src/lib/prototype.ts b/examples/sandbox/src/lib/prototype.ts index 11b75ada..6f43173d 100644 --- a/examples/sandbox/src/lib/prototype.ts +++ b/examples/sandbox/src/lib/prototype.ts @@ -1,8 +1,8 @@ -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export function parse(this: S, u: unknown) { if (this(u)) return u else throw Error('invalid input') } -export const prototype = { parse } +export const bindParse = { parse } diff --git a/examples/sandbox/src/lib/react.ts b/examples/sandbox/src/lib/react.ts index 16e344fc..ca238b12 100644 --- a/examples/sandbox/src/lib/react.ts +++ b/examples/sandbox/src/lib/react.ts @@ -1,5 +1,5 @@ import type * as React from 'react' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export interface Key extends t.union<[t.null, t.string]> { } export const Key = t.union(t.null, t.string) satisfies Key diff --git a/examples/sandbox/src/lib/set.ts b/examples/sandbox/src/lib/set.ts index 2106dc6e..84d85894 100644 --- a/examples/sandbox/src/lib/set.ts +++ b/examples/sandbox/src/lib/set.ts @@ -1,5 +1,5 @@ import * as T from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import type { ValidationError, ValidationFn, @@ -40,7 +40,7 @@ export namespace set { export function def(x: S): set { type T = Set - const predicate = t.isPredicate(x) ? x : (_?: any) => true + const predicate = T.isPredicate(x) ? x : (_?: any) => true function SetSchema(u: unknown): u is T { if (!(u instanceof globalThis.Set)) return false else { diff --git a/examples/sandbox/src/lib/shared.ts b/examples/sandbox/src/lib/shared.ts index 3d658d3a..eb4daf74 100644 --- a/examples/sandbox/src/lib/shared.ts +++ b/examples/sandbox/src/lib/shared.ts @@ -1,7 +1,7 @@ import type { HKT } from '@traversable/registry' import { NS, URI as URI_, symbol as symbol_ } from '@traversable/registry' import { has } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import type { set } from './set' import type { map } from './map' diff --git a/examples/sandbox/src/lib/toHtml.tsx b/examples/sandbox/src/lib/toHtml.tsx index 1794002d..64546cff 100644 --- a/examples/sandbox/src/lib/toHtml.tsx +++ b/examples/sandbox/src/lib/toHtml.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import type * as T from '@traversable/registry' import { fn, parseKey } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { Json } from '@traversable/json' import * as isReact from './react' diff --git a/examples/sandbox/tsconfig.app.json b/examples/sandbox/tsconfig.app.json index d962aa70..0b205611 100644 --- a/examples/sandbox/tsconfig.app.json +++ b/examples/sandbox/tsconfig.app.json @@ -32,8 +32,8 @@ "@traversable/json/*": ["../../packages/json/src/*"], "@traversable/registry": ["../../packages/registry/src"], "@traversable/registry/*": ["../../packages/registry/src/*"], - "@traversable/schema": ["../../packages/schema/src"], - "@traversable/schema/*": ["../../packages/schema/src/*"], + "@traversable/schema-core": ["../../packages/schema-core/src"], + "@traversable/schema-core/*": ["../../packages/schema-core/src/*"], "@traversable/schema-seed": ["../../packages/schema-seed/src"], "@traversable/schema-seed/*": ["../../packages/schema-seed/src/*"], "@traversable/schema-to-json-schema": ["../../packages/schema-to-json-schema/src"], diff --git a/examples/sandbox/vite.config.ts b/examples/sandbox/vite.config.ts index a38e2595..a1147956 100644 --- a/examples/sandbox/vite.config.ts +++ b/examples/sandbox/vite.config.ts @@ -11,7 +11,7 @@ export default defineConfig({ '@traversable/derive-validators': fileURLToPath(new URL('../../packages/derive-validators/src', import.meta.url)), '@traversable/json': fileURLToPath(new URL('../../packages/json/src', import.meta.url)), '@traversable/registry': fileURLToPath(new URL('../../packages/registry/src', import.meta.url)), - '@traversable/schema': fileURLToPath(new URL('../../packages/schema/src', import.meta.url)), + '@traversable/schema-core': fileURLToPath(new URL('../../packages/schema-core/src', import.meta.url)), '@traversable/schema-seed': fileURLToPath(new URL('../../packages/schema-seed/src', import.meta.url)), '@traversable/schema-to-json-schema': fileURLToPath(new URL('../../packages/schema-to-json-schema/src', import.meta.url)), '@traversable/schema-to-string': fileURLToPath(new URL('../../packages/schema-to-string/src', import.meta.url)), diff --git a/packages/derive-codec/package.json b/packages/derive-codec/package.json index f44676d5..dba79328 100644 --- a/packages/derive-codec/package.json +++ b/packages/derive-codec/package.json @@ -48,19 +48,19 @@ }, "peerDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "peerDependenciesMeta": { "@traversable/registry": { "optional": false }, - "@traversable/schema": { + "@traversable/schema-core": { "optional": false } }, "devDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^" } } diff --git a/packages/derive-codec/src/__generated__/__manifest__.ts b/packages/derive-codec/src/__generated__/__manifest__.ts index c7c375f5..1426835d 100644 --- a/packages/derive-codec/src/__generated__/__manifest__.ts +++ b/packages/derive-codec/src/__generated__/__manifest__.ts @@ -42,19 +42,19 @@ export default { }, "peerDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "peerDependenciesMeta": { "@traversable/registry": { "optional": false }, - "@traversable/schema": { + "@traversable/schema-core": { "optional": false } }, "devDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^" } } as const \ No newline at end of file diff --git a/packages/derive-codec/src/bind.ts b/packages/derive-codec/src/bind.ts deleted file mode 100644 index 3e74fb89..00000000 --- a/packages/derive-codec/src/bind.ts +++ /dev/null @@ -1,77 +0,0 @@ -import type { Parameters } from '@traversable/registry' - -import { - t, - t_never, - t_unknown, - t_any, - t_void, - t_null, - t_undefined, - t_symbol, - t_boolean, - t_integer, - t_bigint, - t_number, - t_string, - t_eq, - t_optional, - t_array, - t_record, - t_union, - t_intersect, - t_tuple, - t_object, - // t_of, - // def, -} from '@traversable/schema' -import { pipe } from './codec.js' - -const Object_assign = globalThis.Object.assign - -const def = { - never: t.never.def, - any: t.any.def, - unknown: t.unknown.def, - void: t.void.def, - null: t.null.def, - undefined: t.undefined.def, - symbol: t.symbol.def, - boolean: t.boolean.def, - integer: t.integer.def, - bigint: t.bigint.def, - number: t.number.def, - string: t.string.def, - eq: t.eq.def, - optional: t.optional.def, - array: t.array.def, - record: t.record.def, - union: t.union.def, - intersect: t.intersect.def, - tuple: t.tuple.def, - object: t.object.def, - of: t.of.def, -} - -export function bind() { - void Object_assign(t_never, pipe(t.never)); - void Object_assign(t_unknown, pipe(t.unknown)); - void Object_assign(t_any, pipe(t.any)); - void Object_assign(t_void, pipe(t.void)); - void Object_assign(t_null, pipe(t.null)); - void Object_assign(t_undefined, pipe(t.undefined)); - void Object_assign(t_boolean, pipe(t.boolean)); - void Object_assign(t_symbol, pipe(t.symbol)); - void Object_assign(t_integer, pipe(t.integer)); - void Object_assign(t_bigint, pipe(t.bigint)); - void Object_assign(t_number, pipe(t.number)); - void Object_assign(t_string, pipe(t.string)); - void ((t_eq.def as any) = (...args: Parameters) => pipe(def.eq(...args))); - void ((t_optional.def as any) = (...args: Parameters) => pipe(def.optional(...args))); - void ((t_record.def as any) = (...args: Parameters) => pipe(def.record(...args))); - void ((t_array.def as any) = (...args: Parameters) => pipe(def.array(...args))); - void ((t_union.def as any) = (...args: Parameters) => pipe(def.union(...args))); - void ((t_intersect.def as any) = (...args: Parameters) => pipe(def.intersect(...args))); - void ((t_tuple.def as any) = (...args: Parameters) => pipe(def.tuple(...args))); - void ((t_object.def as any) = (...args: Parameters) => pipe(def.object(...args))); -} diff --git a/packages/derive-codec/src/codec.ts b/packages/derive-codec/src/codec.ts index c7cf4378..b1a4ba54 100644 --- a/packages/derive-codec/src/codec.ts +++ b/packages/derive-codec/src/codec.ts @@ -1,4 +1,5 @@ -import type { t } from '@traversable/schema' +import type { Unknown } from '@traversable/registry' +import type { t } from '@traversable/schema-core' /** @internal */ interface Pipelines { @@ -8,19 +9,31 @@ interface Pipelines { } export const Invariant = { - DecodeError: (u: unknown) => globalThis.Error('DecodeError: could not decode invalid input, got: \n\r' + JSON.stringify(u, null, 2)) + DecodeError: (u: unknown) => + Error('DecodeError: could not decode invalid input, got: \n\r' + JSON.stringify(u, null, 2)) } as const export interface Pipe { unpipe(mapBack: (b: B) => T): Codec } - export interface Extend { unextend(mapBack: (s: S) => B): Codec } +export interface BindCodec { + pipe(map: (src: T['_type' & keyof T]) => B): + Pipe + extend(premap: (b: B) => T['_type' & keyof T]): + Extend +} + +export let bindCodec = { + pipe(this: S, mapfn: (src: S['_type']) => B) { return Codec.new(this).pipe(mapfn) }, + extend(this: S, mapfn: (src: S['_type']) => B) { return Codec.new(this).extend(mapfn) }, +} satisfies BindCodec + export class Codec { static new - : (schema: S) => S & { codec: Codec } - = (schema) => { const codec = new Codec(schema); Object.defineProperty(schema, 'codec', { value: codec, writable: true }); return schema as never } + : (schema: S) => Codec + = (schema) => new Codec(schema) - parse(u: S | {} | null | undefined): T | Error { + parse(u: S | Unknown): T | Error { if (typeof this.schema === 'function' && this.schema(u) === false) return Invariant.DecodeError(u) else return this.decode(u as S) @@ -29,7 +42,8 @@ export class Codec { decode(source: S): T decode(source: S) { this._.$ = source - for (let ix = 0, len = this._.to.length; ix < len; ix++) { + let len = this._.to.length + for (let ix = 0; ix < len; ix++) { const f = this._.to[ix] this._.$ = f(this._.$) } @@ -39,7 +53,8 @@ export class Codec { encode(target: T): S encode(target: T) { this._.$ = target - for (let ix = this._.from.length; ix-- !== 0;) { + let len = this._.from.length + for (let ix = len; ix-- !== 0;) { const f = this._.from[ix] this._.$ = f(this._.$) } @@ -68,21 +83,8 @@ export class Codec { } } - private constructor( + constructor( public schema: A, private _: Pipelines = { from: [], to: [], $: void 0 } ) { } } - -export interface pipe { - codec: { - pipe(map: (src: T['_type' & keyof T]) => B): - Pipe - extend(premap: (b: B) => T['_type' & keyof T]): - Extend - } -} - -export function pipe(schema: S): pipe { - return Codec.new(schema) -} diff --git a/packages/derive-codec/src/exports.ts b/packages/derive-codec/src/exports.ts index ff1a5fff..705f9570 100644 --- a/packages/derive-codec/src/exports.ts +++ b/packages/derive-codec/src/exports.ts @@ -5,5 +5,4 @@ export type { } from './codec.js' export { Codec, - pipe, } from './codec.js' diff --git a/packages/derive-codec/src/install.ts b/packages/derive-codec/src/install.ts index 8dd38fac..97f04836 100644 --- a/packages/derive-codec/src/install.ts +++ b/packages/derive-codec/src/install.ts @@ -1,30 +1,58 @@ -import type { t } from '@traversable/schema' -import type { pipe } from './codec.js' +import { Object_assign } from '@traversable/registry' +import { t } from '@traversable/schema-core' -import { bind } from './bind.js' -// SIDE-EFFECT -void bind() +import type { BindCodec } from './codec.js' +import { bindCodec } from './codec.js' -declare module '@traversable/schema' { - interface t_LowerBound extends pipe> { } - interface t_never extends pipe { } - interface t_unknown extends pipe { } - interface t_void extends pipe { } - interface t_any extends pipe { } - interface t_null extends pipe { } - interface t_undefined extends pipe { } - interface t_symbol extends pipe { } - interface t_boolean extends pipe { } - interface t_integer extends pipe { } - interface t_bigint extends pipe { } - interface t_number extends pipe { } - interface t_string extends pipe { } - interface t_eq extends pipe> { } - interface t_optional extends pipe> { } - interface t_array extends pipe> { } - interface t_record extends pipe> { } - interface t_union extends pipe> { } - interface t_intersect extends pipe> { } - interface t_tuple extends pipe> { } - interface t_object extends pipe> { } +declare module '@traversable/schema-core' { + interface t_LowerBound extends BindCodec> { } + // interface t_never extends BindCodec { } + interface t_unknown extends BindCodec { } + interface t_void extends BindCodec { } + interface t_any extends BindCodec { } + interface t_null extends BindCodec { } + interface t_undefined extends BindCodec { } + interface t_symbol extends BindCodec { } + interface t_boolean extends BindCodec { } + interface t_integer extends BindCodec { } + interface t_bigint extends BindCodec { } + interface t_number extends BindCodec { } + interface t_string extends BindCodec { } + interface t_eq extends BindCodec> { } + interface t_optional extends BindCodec> { } + interface t_array extends BindCodec> { } + interface t_record extends BindCodec> { } + interface t_union extends BindCodec> { } + interface t_intersect extends BindCodec> { } + interface t_tuple extends BindCodec> { } + interface t_object extends BindCodec> { } +} + +///////////////// +/// INSTALL /// +void bind() /// +/// INSTALL /// +///////////////// + +export function bind() { + Object_assign(t.never, bindCodec) + Object_assign(t.unknown, bindCodec) + Object_assign(t.any, bindCodec) + Object_assign(t.void, bindCodec) + Object_assign(t.null, bindCodec) + Object_assign(t.undefined, bindCodec) + Object_assign(t.boolean, bindCodec) + Object_assign(t.symbol, bindCodec) + Object_assign(t.integer, bindCodec) + Object_assign(t.bigint, bindCodec) + Object_assign(t.number, bindCodec) + Object_assign(t.string, bindCodec) + Object_assign(t.eq.userDefinitions, bindCodec) + Object_assign(t.optional.userDefinitions, bindCodec) + Object_assign(t.array.userDefinitions, bindCodec) + Object_assign(t.record.userDefinitions, bindCodec) + Object_assign(t.union.userDefinitions, bindCodec) + Object_assign(t.intersect.userDefinitions, bindCodec) + Object_assign(t.tuple.userDefinitions, bindCodec) + Object_assign(t.object.userDefinitions, bindCodec) } diff --git a/packages/derive-codec/test/codec.test.ts b/packages/derive-codec/test/codec.test.ts index 4f970c59..afd805b1 100644 --- a/packages/derive-codec/test/codec.test.ts +++ b/packages/derive-codec/test/codec.test.ts @@ -1,7 +1,7 @@ import * as vi from 'vitest' import { Codec } from '@traversable/derive-codec' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' vi.describe('〖⛳️〗‹‹‹ ❲@traverable/derive-codec❳', () => { vi.it('〖⛳️〗› ❲Codec❳', () => { @@ -35,7 +35,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/derive-codec❳', () => { }] satisfies [any] const codec_01 = Codec - .new(ServerUser).codec + .new(ServerUser) .extend(({ data }: { data: ServerUser }) => data) .unextend((_) => ({ data: _ })) .extend(([_]: [{ data: ServerUser }]) => _) @@ -51,4 +51,3 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/derive-codec❳', () => { vi.assert.deepEqual(codec_01.encode(codec_01.decode(codec_01.encode(codec_01.decode(serverResponse)))), serverResponse) }) }) - diff --git a/packages/derive-codec/test/install.test.ts b/packages/derive-codec/test/install.test.ts index 61b7c7b6..56fd6077 100644 --- a/packages/derive-codec/test/install.test.ts +++ b/packages/derive-codec/test/install.test.ts @@ -1,12 +1,41 @@ -import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { assert, it, describe, vi } from 'vitest' -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/derive-codec❳', () => { - vi.it('〖⛳️〗› ❲pre-install❳', () => vi.assert.isFalse(t.has('codec')(t.string))) +import { t } from '@traversable/schema-core' - vi.it('〖⛳️〗› ❲post-install❳', () => { - import('@traversable/derive-codec/install') - .then(() => vi.assert.isTrue(t.has('codec')(t.string))) - .catch((e) => vi.assert.fail(e.message)) +describe('〖⛳️〗‹‹‹ ❲@traversable/derive-codec❳', async () => { + it('〖⛳️〗› ❲pre-install❳', async () => { + assert.isFalse(t.has('pipe')(t.string)) + assert.isFalse(t.has('extend')(t.string)) + }) + + it('〖⛳️〗› ❲post-install❳', async () => { + assert.isFalse(t.has('pipe')(t.string)) + assert.isFalse(t.has('extend')(t.string)) + + await vi.waitFor(() => import('@traversable/derive-codec/install')) + + assert.isTrue(t.has('pipe')(t.string)) + assert.isTrue(t.has('extend')(t.string)) + + let codec_01 = t.array(t.string) + .pipe((ss) => ss.map(Number)) + .unpipe((xs) => xs.map(String)) + + assert.deepEqual(codec_01.decode(['1', '2', '3']), [1, 2, 3]) + assert.deepEqual(codec_01.encode([1, 2, 3]), ['1', '2', '3']) + + let codec_02 = t.array(t.array(t.integer)) + .pipe((xss) => xss.map((xs) => xs.map((x) => [x] satisfies [any]))) + .unpipe((yss) => yss.map((ys) => ys.map(([y]) => y))) + .pipe((xss) => xss.map((xs) => xs.map(([x]) => [x - 1] satisfies [any]))) + .unpipe((yss) => yss.map((ys) => ys.map(([y]) => [y + 1]))) + + assert.deepEqual(codec_02.decode( + [[0, 1, 2], [3, 4, 5], [6, 7, 8]] + ), [[[-1], [0], [1]], [[2], [3], [4]], [[5], [6], [7]]]) + + assert.deepEqual(codec_02.encode( + [[[-1], [0], [1]], [[2], [3], [4]], [[5], [6], [7]]] + ), [[0, 1, 2], [3, 4, 5], [6, 7, 8]]) }) }) diff --git a/packages/derive-codec/tsconfig.build.json b/packages/derive-codec/tsconfig.build.json index cabdfce7..2972409b 100644 --- a/packages/derive-codec/tsconfig.build.json +++ b/packages/derive-codec/tsconfig.build.json @@ -9,6 +9,6 @@ }, "references": [ { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ] } diff --git a/packages/derive-codec/tsconfig.src.json b/packages/derive-codec/tsconfig.src.json index fa90f525..9416bd78 100644 --- a/packages/derive-codec/tsconfig.src.json +++ b/packages/derive-codec/tsconfig.src.json @@ -8,7 +8,7 @@ }, "references": [ { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ], "include": ["src"] } diff --git a/packages/derive-codec/tsconfig.test.json b/packages/derive-codec/tsconfig.test.json index 80aa9583..3ad0d988 100644 --- a/packages/derive-codec/tsconfig.test.json +++ b/packages/derive-codec/tsconfig.test.json @@ -9,7 +9,7 @@ "references": [ { "path": "tsconfig.src.json" }, { "path": "../registry" }, - { "path": "../schema" }, + { "path": "../schema-core" }, { "path": "../schema-seed" } ], "include": ["test"] diff --git a/packages/derive-equals/package.json b/packages/derive-equals/package.json index 070d2f37..89bea69c 100644 --- a/packages/derive-equals/package.json +++ b/packages/derive-equals/package.json @@ -46,7 +46,7 @@ "peerDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "peerDependenciesMeta": { "@traversable/json": { @@ -55,14 +55,14 @@ "@traversable/registry": { "optional": false }, - "@traversable/schema": { + "@traversable/schema-core": { "optional": false } }, "devDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^", "@traversable/schema-to-string": "workspace:^" } diff --git a/packages/derive-equals/src/__generated__/__manifest__.ts b/packages/derive-equals/src/__generated__/__manifest__.ts index 19d271aa..03966b74 100644 --- a/packages/derive-equals/src/__generated__/__manifest__.ts +++ b/packages/derive-equals/src/__generated__/__manifest__.ts @@ -42,7 +42,7 @@ export default { "peerDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "peerDependenciesMeta": { "@traversable/json": { @@ -51,14 +51,14 @@ export default { "@traversable/registry": { "optional": false }, - "@traversable/schema": { + "@traversable/schema-core": { "optional": false } }, "devDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^", "@traversable/schema-to-string": "workspace:^" } diff --git a/packages/derive-equals/src/equals.ts b/packages/derive-equals/src/equals.ts index 2fc46842..646f757e 100644 --- a/packages/derive-equals/src/equals.ts +++ b/packages/derive-equals/src/equals.ts @@ -1,7 +1,7 @@ import type { Algebra, Kind } from '@traversable/registry' import { Equal, fn, URI } from '@traversable/registry' import type { Json } from '@traversable/json' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' /** @internal */ type FixUnknown = 0 extends T & 1 ? unknown : T diff --git a/packages/derive-equals/src/install.ts b/packages/derive-equals/src/install.ts index 2ddb2716..23c2cb55 100644 --- a/packages/derive-equals/src/install.ts +++ b/packages/derive-equals/src/install.ts @@ -1,15 +1,20 @@ -import { Equal } from '@traversable/registry' -import { t } from '@traversable/schema' - -import * as Eq from './equals.js' +import { + Array_isArray, + Equal, + Object_assign, + Object_hasOwn, + Object_is, + Object_keys, +} from '@traversable/registry' +import { t } from '@traversable/schema-core' export interface equals { equals: Equal } -declare module '@traversable/schema' { +declare module '@traversable/schema-core' { interface t_LowerBound extends equals { } - interface t_never extends equals { } + // interface t_never extends equals { } interface t_unknown extends equals { } interface t_void extends equals { } interface t_any extends equals { } @@ -31,23 +36,7 @@ declare module '@traversable/schema' { interface t_object extends equals { } } -/** @internal */ -const Object_assign = globalThis.Object.assign - -/** @internal */ -const Object_keys = globalThis.Object.keys - -/** @internal */ -const Array_isArray = globalThis.Array.isArray - -/** @internal */ -const Object_is = globalThis.Object.is - -/** @internal */ -const hasOwn = (u: unknown, k: K): u is Record => - !!u && typeof u === 'object' && globalThis.Object.prototype.hasOwnProperty.call(u, k) - -export function eqEquals(this: any, x: V, y: V): boolean { return t.eq.def(x)(y) } +export function eqEquals(this: t.eq, x: V, y: V): boolean { return t.eq.def(x)(y) } export function optionalEquals( this: t.optional<{ equals: Equal }>, @@ -89,19 +78,20 @@ export function recordEquals( if (len !== rhs.length) return false for (let ix = len; ix-- !== 0;) { k = lhs[ix] - if (!hasOwn(r, k)) return false + if (!Object_hasOwn(r, k)) return false if (!(this.def.equals(l[k], r[k]))) return false } len = rhs.length for (let ix = len; ix-- !== 0;) { k = rhs[ix] - if (!hasOwn(l, k)) return false + if (!Object_hasOwn(l, k)) return false if (!(this.def.equals(l[k], r[k]))) return false } return true } -export function unionEquals(this: t.union<{ equals: Equal }[]>, +export function unionEquals( + this: t.union<{ equals: Equal }[]>, l: unknown, r: unknown ): boolean { @@ -131,10 +121,10 @@ export function tupleEquals( if (Array_isArray(l)) { if (!Array_isArray(r)) return false for (let ix = this.def.length; ix-- !== 0;) { - if (!hasOwn(l, ix) && !hasOwn(r, ix)) continue - if (hasOwn(l, ix) && !hasOwn(r, ix)) return false - if (!hasOwn(l, ix) && hasOwn(r, ix)) return false - if (hasOwn(l, ix) && hasOwn(r, ix)) { + if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue + if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false + if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false + if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { if (!this.def[ix].equals(l[ix], r[ix])) return false } } @@ -152,8 +142,8 @@ export function objectEquals( if (!l || typeof l !== 'object' || Array_isArray(l)) return false if (!r || typeof r !== 'object' || Array_isArray(r)) return false for (const k in this.def) { - const lHas = hasOwn(l, k) - const rHas = hasOwn(r, k) + const lHas = Object_hasOwn(l, k) + const rHas = Object_hasOwn(r, k) if (lHas) { if (!rHas) return false if (!this.def[k].equals(l[k], r[k])) return false @@ -173,9 +163,6 @@ void bind() /// /// INSTALL /// ///////////////// - -function stringEquals(l: never, r: never) { return Object_is(l, r) } - export function bind() { Object_assign(t.never, { equals: function neverEquals(l: never, r: never) { return Object_is(l, r) } }) Object_assign(t.unknown, { equals: function unknownEquals(l: never, r: never) { return Object_is(l, r) } }) @@ -188,17 +175,13 @@ export function bind() { Object_assign(t.integer, { equals: function integerEquals(l: never, r: never) { return Object_is(l, r) } }) Object_assign(t.bigint, { equals: function bigintEquals(l: never, r: never) { return Object_is(l, r) } }) Object_assign(t.number, { equals: function numberEquals(l: never, r: never) { return Object_is(l, r) } }) - Object_assign(t.string, { equals: stringEquals.bind(t.string) }) - Object_assign(t.eq.prototype, { equals: eqEquals }) - - // let test = arrayEquals.bind(t.array.prototype) - t.array.prototype.equals = arrayEquals - t.record.prototype.equals = recordEquals - // Object_assign(t.array.prototype, { equals: arrayEquals.bind(t.array.prototype) }) - // Object_assign(t.record.prototype, { equals: recordEquals }) - Object_assign(t.optional.prototype, { equals: optionalEquals }) - Object_assign(t.union.prototype, { equals: unionEquals }) - Object_assign(t.intersect.prototype, { equals: intersectEquals }) - Object_assign(t.tuple.prototype, { equals: tupleEquals }) - Object_assign(t.object.prototype, { equals: objectEquals }) + Object_assign(t.string, { equals: function stringEquals(l: never, r: never) { return Object_is(l, r) } }) + Object_assign(t.eq.userDefinitions, { equals: eqEquals }) + Object_assign(t.array.userDefinitions, { equals: arrayEquals }) + Object_assign(t.record.userDefinitions, { equals: recordEquals }) + Object_assign(t.optional.userDefinitions, { equals: optionalEquals }) + Object_assign(t.union.userDefinitions, { equals: unionEquals }) + Object_assign(t.intersect.userDefinitions, { equals: intersectEquals }) + Object_assign(t.tuple.userDefinitions, { equals: tupleEquals }) + Object_assign(t.object.userDefinitions, { equals: objectEquals }) } diff --git a/packages/derive-equals/test/equals.test.ts b/packages/derive-equals/test/equals.test.ts index a08e6d89..4a7cf067 100644 --- a/packages/derive-equals/test/equals.test.ts +++ b/packages/derive-equals/test/equals.test.ts @@ -4,7 +4,7 @@ import * as NodeJSUtil from 'node:util' import type { Algebra, Kind } from '@traversable/registry' import { Equal, fn, omitMethods, URI } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { Seed } from '@traversable/schema-seed' import { Eq } from '@traversable/derive-equals' diff --git a/packages/derive-equals/test/install.test.ts b/packages/derive-equals/test/install.test.ts index 02d25347..1a409bdd 100644 --- a/packages/derive-equals/test/install.test.ts +++ b/packages/derive-equals/test/install.test.ts @@ -1,12 +1,33 @@ -import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { assert, describe, it, vi } from 'vitest' +import { t } from '@traversable/schema-core' -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/derive-equals❳', () => { - vi.it('〖⛳️〗› ❲pre-install❳', () => vi.assert.isFalse(t.has('equals')(t.string))) +describe('〖⛳️〗‹‹‹ ❲@traversable/derive-equals❳', async () => { + it('〖⛳️〗› ❲pre-install❳', () => assert.isFalse(t.has('equals')(t.string))) - vi.it('〖⛳️〗› ❲post-install❳', () => { - import('@traversable/derive-equals/install') - .then(() => vi.assert.isTrue(t.has('equals')(t.string))) - .catch((e) => vi.assert.fail(e.message)) + it('〖⛳️〗› ❲post-install❳', async () => { + assert.isFalse(t.has('equals')(t.string)) + assert.isFalse(t.has('equals')(t.array(t.string))) + + await vi.waitFor(() => import('@traversable/derive-equals/install')) + + assert.isTrue(t.has('equals')(t.string)) + assert.isTrue(t.has('equals')(t.array(t.string))) + + assert.isTrue(t.string.equals('', '')) + assert.isFalse(t.string.equals('a', 'b')) + + assert.isTrue(t.array(t.string).equals([], [])) + assert.isTrue(t.array(t.string).equals([''], [''])) + assert.isFalse(t.array(t.string).equals(['a'], [])) + assert.isFalse(t.array(t.string).equals(['a'], ['b'])) + + assert.isTrue(t.array(t.array(t.string)).equals([], [])) + assert.isTrue(t.array(t.array(t.string)).equals([[]], [[]])) + assert.isTrue(t.array(t.array(t.string)).equals([['a']], [['a']])) + assert.isTrue(t.array(t.array(t.string)).equals([[], ['b']], [[], ['b']])) + + assert.isFalse(t.array(t.array(t.string)).equals([[]], [['c']])) + assert.isFalse(t.array(t.array(t.string)).equals([['d']], [['e']])) + assert.isFalse(t.array(t.array(t.string)).equals([['f']], [['f', 'g']])) }) }) diff --git a/packages/derive-equals/tsconfig.build.json b/packages/derive-equals/tsconfig.build.json index 30eb210a..5dac57a7 100644 --- a/packages/derive-equals/tsconfig.build.json +++ b/packages/derive-equals/tsconfig.build.json @@ -10,6 +10,6 @@ "references": [ { "path": "../json" }, { "path": "../registry" }, - { "path": "../schema" }, + { "path": "../schema-core" }, ] } diff --git a/packages/derive-equals/tsconfig.src.json b/packages/derive-equals/tsconfig.src.json index 0f1e77bf..acd96694 100644 --- a/packages/derive-equals/tsconfig.src.json +++ b/packages/derive-equals/tsconfig.src.json @@ -9,7 +9,7 @@ "references": [ { "path": "../json" }, { "path": "../registry" }, - { "path": "../schema" }, + { "path": "../schema-core" }, ], "include": ["src"] } diff --git a/packages/derive-equals/tsconfig.test.json b/packages/derive-equals/tsconfig.test.json index 77da0195..aa484eed 100644 --- a/packages/derive-equals/tsconfig.test.json +++ b/packages/derive-equals/tsconfig.test.json @@ -10,7 +10,7 @@ { "path": "tsconfig.src.json" }, { "path": "../json" }, { "path": "../registry" }, - { "path": "../schema" }, + { "path": "../schema-core" }, { "path": "../schema-seed" }, { "path": "../schema-to-string" }, ], diff --git a/packages/derive-validators/package.json b/packages/derive-validators/package.json index 952cfda2..f8301dac 100644 --- a/packages/derive-validators/package.json +++ b/packages/derive-validators/package.json @@ -49,12 +49,12 @@ "peerDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^" } } diff --git a/packages/derive-validators/src/__generated__/__manifest__.ts b/packages/derive-validators/src/__generated__/__manifest__.ts index e2451c33..05818dcd 100644 --- a/packages/derive-validators/src/__generated__/__manifest__.ts +++ b/packages/derive-validators/src/__generated__/__manifest__.ts @@ -43,12 +43,12 @@ export default { "peerDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^" } } as const \ No newline at end of file diff --git a/packages/derive-validators/src/errors.ts b/packages/derive-validators/src/errors.ts index c8738280..c669a6ce 100644 --- a/packages/derive-validators/src/errors.ts +++ b/packages/derive-validators/src/errors.ts @@ -1,4 +1,4 @@ -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export interface ValidationError { kind: string diff --git a/packages/derive-validators/src/install.ts b/packages/derive-validators/src/install.ts index 2528bd7d..9251145b 100644 --- a/packages/derive-validators/src/install.ts +++ b/packages/derive-validators/src/install.ts @@ -1,31 +1,32 @@ -import { t } from '@traversable/schema' -import * as proto from './prototype.js' -import type { Validate } from './shared.js' +import { Object_assign } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import * as validate from './validate.js' +import type { ValidationFn_ as ValidationFn } from './shared.js' -declare module '@traversable/schema' { - interface t_Lower { validate: Validate } - interface t_never { validate: Validate } - interface t_unknown { validate: Validate } - interface t_void { validate: Validate } - interface t_any { validate: Validate } - interface t_null { validate: Validate } - interface t_undefined { validate: Validate } - interface t_symbol { validate: Validate } - interface t_boolean { validate: Validate } - interface t_integer { validate: Validate } - interface t_bigint { validate: Validate } - interface t_number { validate: Validate } - interface t_string { validate: Validate } - interface t_eq { validate: Validate } - interface t_optional { validate: Validate> } - interface t_array { validate: Validate> } - interface t_record { validate: Validate> } - interface t_union { validate: Validate> } - interface t_intersect { validate: Validate> } - interface t_tuple { validate: Validate> } - interface t_object { validate: Validate> } - interface t_of { validate: Validate> } - interface t_enum { validate: Validate> } +declare module '@traversable/schema-core' { + interface t_Lower { validate: ValidationFn } + // interface t_never { validate: ValidationFn } + interface t_unknown { validate: ValidationFn } + interface t_void { validate: ValidationFn } + interface t_any { validate: ValidationFn } + interface t_null { validate: ValidationFn } + interface t_undefined { validate: ValidationFn } + interface t_symbol { validate: ValidationFn } + interface t_boolean { validate: ValidationFn } + interface t_integer { validate: ValidationFn } + interface t_bigint { validate: ValidationFn } + interface t_number { validate: ValidationFn } + interface t_string { validate: ValidationFn } + interface t_eq { validate: ValidationFn } + interface t_optional { validate: ValidationFn } + interface t_array { validate: ValidationFn } + interface t_record { validate: ValidationFn } + interface t_union { validate: ValidationFn } + interface t_intersect { validate: ValidationFn } + interface t_tuple { validate: ValidationFn } + interface t_object { validate: ValidationFn } + interface t_of { validate: ValidationFn } + interface t_enum { validate: ValidationFn } } ///////////////// @@ -34,29 +35,26 @@ void bind() /// /// INSTALL /// ///////////////// - export function bind() { - /** @internal */ - let Object_assign = globalThis.Object.assign - Object_assign(t.never, { validate: proto.never }) - Object_assign(t.unknown, { validate: proto.unknown }) - Object_assign(t.any, { validate: proto.any }) - Object_assign(t.void, { validate: proto.void }) - Object_assign(t.null, { validate: proto.null }) - Object_assign(t.undefined, { validate: proto.undefined }) - Object_assign(t.symbol, { validate: proto.symbol }) - Object_assign(t.boolean, { validate: proto.boolean }) - Object_assign(t.integer, { validate: proto.integer }) - Object_assign(t.bigint, { validate: proto.bigint }) - Object_assign(t.number, { validate: proto.number }) - Object_assign(t.string, { validate: proto.string }) - Object_assign(t.optional.prototype, { validate: proto.optional }) - Object_assign(t.eq.prototype, { validate: proto.eq }) - Object_assign(t.array.prototype, { validate: proto.array }) - Object_assign(t.record.prototype, { validate: proto.record }) - Object_assign(t.union.prototype, { validate: proto.union }) - Object_assign(t.intersect.prototype, { validate: proto.intersect }) - Object_assign(t.tuple.prototype, { validate: proto.tuple }) - Object_assign(t.object.prototype, { validate: proto.object }) - Object_assign(t.enum.prototype, { validate: proto.enum }) + Object_assign(t.never, { validate: validate.never }) + Object_assign(t.unknown, { validate: validate.unknown }) + Object_assign(t.any, { validate: validate.any }) + Object_assign(t.void, { validate: validate.void }) + Object_assign(t.null, { validate: validate.null }) + Object_assign(t.undefined, { validate: validate.undefined }) + Object_assign(t.symbol, { validate: validate.symbol }) + Object_assign(t.boolean, { validate: validate.boolean }) + Object_assign(t.integer, { validate: validate.integer }) + Object_assign(t.bigint, { validate: validate.bigint }) + Object_assign(t.number, { validate: validate.number }) + Object_assign(t.string, { validate: validate.string }) + Object_assign(t.eq.userDefinitions, { validate: validate.eq }) + Object_assign(t.optional.userDefinitions, { validate: validate.optional }) + Object_assign(t.array.userDefinitions, { validate: validate.array }) + Object_assign(t.record.userDefinitions, { validate: validate.record }) + Object_assign(t.union.userDefinitions, { validate: validate.union }) + Object_assign(t.intersect.userDefinitions, { validate: validate.intersect }) + Object_assign(t.tuple.userDefinitions, { validate: validate.tuple }) + Object_assign(t.object.userDefinitions, { validate: validate.object }) + Object_assign(t.enum.userDefinitions, { validate: validate.enum }) } diff --git a/packages/derive-validators/src/recursive.ts b/packages/derive-validators/src/recursive.ts index a1b3cd9b..10ade016 100644 --- a/packages/derive-validators/src/recursive.ts +++ b/packages/derive-validators/src/recursive.ts @@ -1,6 +1,6 @@ import type { IndexedAlgebra } from '@traversable/registry' import { Equal, fn, symbol, typeName, URI } from '@traversable/registry' -import { t, getConfig } from '@traversable/schema' +import { t, getConfig } from '@traversable/schema-core' import type { ValidationError } from './errors.js' import { BOUNDS, ERROR, UNARY } from './errors.js' diff --git a/packages/derive-validators/src/shared.ts b/packages/derive-validators/src/shared.ts index 852f8407..71960540 100644 --- a/packages/derive-validators/src/shared.ts +++ b/packages/derive-validators/src/shared.ts @@ -1,7 +1,7 @@ import type { Unknown } from '@traversable/registry' import { has, symbol } from '@traversable/registry' -import type { t, SchemaOptions } from '@traversable/schema' +import type { t, SchemaOptions } from '@traversable/schema-core' import type { ValidationError } from './errors.js' export interface Options extends SchemaOptions { @@ -12,6 +12,8 @@ export type Validate = never | { (u: T | Unknown): true | ValidationError[] } export type ValidationFn = never | { (u: T | Unknown, path?: (keyof any)[]): true | ValidationError[] } export interface Validator { validate: ValidationFn } +export type ValidationFn_ = never | { (u: S['_type' & keyof S] | Unknown, path?: (keyof any)[]): true | ValidationError[] } + export let hasOptionalSymbol = (u: unknown): u is t.optional => !!u && typeof u === 'function' && symbol.optional in u && typeof u[symbol.optional] === 'number' diff --git a/packages/derive-validators/src/prototype.ts b/packages/derive-validators/src/validate.ts similarity index 65% rename from packages/derive-validators/src/prototype.ts rename to packages/derive-validators/src/validate.ts index d8678d0d..42aae406 100644 --- a/packages/derive-validators/src/prototype.ts +++ b/packages/derive-validators/src/validate.ts @@ -1,5 +1,17 @@ -import { Equal, Primitive, typeName, URI } from '@traversable/registry' -import { t, getConfig } from '@traversable/schema' +import type { + Primitive, + Unknown +} from '@traversable/registry' +import { + Array_isArray, + Equal, + Object_hasOwn, + Object_keys, + Object_values, + typeName, + URI, +} from '@traversable/registry' +import { t, getConfig } from '@traversable/schema-core' import type { ValidationError } from './errors.js' import { NULLARY, UNARY, ERROR } from './errors.js' @@ -29,39 +41,30 @@ export { validateObject as object, } -/** @internal */ -let Array_isArray = globalThis.Array.isArray - -/** @internal */ -let Object_keys = globalThis.Object.keys - -/** @internal */ -let hasOwn = (u: unknown, k: K): u is Record => - !!u && typeof u === 'object' && globalThis.Object.prototype.hasOwnProperty.call(u, k) - /** @internal */ let isObject = (u: unknown): u is { [x: string]: unknown } => !!u && typeof u === 'object' && !Array_isArray(u) /** @internal */ -let isKeyOf = (k: keyof any, u: T): k is keyof T => !!u && (typeof u === 'function' || typeof u === 'object') && k in u - -function validateNever(this: t.never, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.never(u, path)] } -function validateUnknown(this: t.unknown, u: unknown, path: (keyof any)[] = []) { return true } -function validateAny(this: t.any, u: unknown, path: (keyof any)[] = []) { return true } -function validateVoid(this: t.void, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.void(u, path)] } -function validateNull(this: t.null, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.null(u, path)] } -function validateUndefined(this: t.undefined, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.undefined(u, path)] } -function validateSymbol(this: t.symbol, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.symbol(u, path)] } -function validateBoolean(this: t.boolean, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.boolean(u, path)] } -function validateBigInt(this: t.bigint, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.bigint(u, path)] } -function validateInteger(this: t.integer, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.integer(u, path)] } -function validateNumber(this: t.number, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.number(u, path)] } -function validateString(this: t.string, u: unknown, path: (keyof any)[] = []) { return this(u) || [NULLARY.string(u, path)] } +let isKeyOf = (k: keyof any, u: T): k is keyof T => + !!u && (typeof u === 'function' || typeof u === 'object') && k in u + +function validateAny(this: t.any, _u: unknown, _path?: (keyof any)[]) { return true } +function validateUnknown(this: t.unknown, _u: unknown, _path?: (keyof any)[]) { return true } +function validateNever(this: t.never, u: unknown, path = Array.of()) { return [NULLARY.never(u, path)] } +function validateVoid(this: t.void, u: unknown, path = Array.of()) { return this(u) || [NULLARY.void(u, path)] } +function validateNull(this: t.null, u: unknown, path = Array.of()) { return this(u) || [NULLARY.null(u, path)] } +function validateUndefined(this: t.undefined, u: unknown, path = Array.of()) { return this(u) || [NULLARY.undefined(u, path)] } +function validateSymbol(this: t.symbol, u: unknown, path = Array.of()) { return this(u) || [NULLARY.symbol(u, path)] } +function validateBoolean(this: t.boolean, u: unknown, path = Array.of()) { return this(u) || [NULLARY.boolean(u, path)] } +function validateBigInt(this: t.bigint, u: unknown, path = Array.of()) { return this(u) || [NULLARY.bigint(u, path)] } +function validateInteger(this: t.integer, u: unknown, path = Array.of()) { return this(u) || [NULLARY.integer(u, path)] } +function validateNumber(this: t.number, u: unknown, path = Array.of()) { return this(u) || [NULLARY.number(u, path)] } +function validateString(this: t.string, u: unknown, path = Array.of()) { return this(u) || [NULLARY.string(u, path)] } -validateNever.tag = URI.never validateAny.tag = URI.any validateUnknown.tag = URI.unknown +validateNever.tag = URI.never validateVoid.tag = URI.void validateNull.tag = URI.null validateUndefined.tag = URI.undefined @@ -71,31 +74,52 @@ validateBigInt.tag = URI.bigint validateInteger.tag = URI.integer validateNumber.tag = URI.number validateString.tag = URI.string - validateEnum.tag = URI.enum -function validateEnum(this: t.enum>, u: unknown, path: (keyof any)[] = []) { - let values = Object.values(this.def) +validateEq.tag = URI.eq +validateArray.tag = URI.array +validateRecord.tag = URI.record +validateUnion.tag = URI.union +validateIntersect.tag = URI.intersect +validateTuple.tag = URI.tuple +validateObject.tag = URI.object +validateOptional.tag = URI.optional +validateOptional.optional = 1 + +function validateEnum( + this: t.enum>, + u: unknown, + path: (keyof any)[] = [], +): true | ValidationError[] { + let values = Object_values(this.def) return values.includes(u as never) || [ERROR.enum(u, path, values.join(', '))] } -validateOptional.optional = 1 -validateOptional.tag = URI.optional -function validateOptional(this: t.optional, u: unknown, path: (keyof any)[] = []) { +function validateOptional( + this: t.optional>, + u: T | Unknown, + path = Array.of(), +): true | ValidationError[] { if (u === void 0) return true return this.def.validate(u, path) } -validateEq.tag = URI.eq -function validateEq(this: t.eq, u: unknown, path: (keyof any)[] = []) { +function validateEq( + this: t.eq, + u: V | Unknown, + path = Array.of(), +): true | ValidationError[] { let options = getConfig().schema let equals = options?.eq?.equalsFn || Equal.lax if (equals(this.def, u)) return true else return [ERROR.eq(u, path, this.def)] } -validateArray.tag = URI.array -function validateArray(this: t.array, u: unknown, path: (keyof any)[] = []) { - if (!Array.isArray(u)) return [NULLARY.array(u, path)] +function validateArray( + this: t.array>, + u: T | Unknown, + path = Array.of(), +): true | ValidationError[] { + if (!Array_isArray(u)) return [NULLARY.array(u, path)] let errors = Array.of() if (t.integer(this.minLength) && u.length < this.minLength) errors.push(ERROR.arrayMinLength(u, path, this.minLength)) if (t.integer(this.maxLength) && u.length > this.maxLength) errors.push(ERROR.arrayMaxLength(u, path, this.maxLength)) @@ -108,8 +132,11 @@ function validateArray(this: t.array, u: unknown, path: return errors.length === 0 || errors } -validateRecord.tag = URI.record -function validateRecord(this: t.record, u: unknown, path: (keyof any)[] = []): true | ValidationError[] { +function validateRecord( + this: t.record>, + u: T | Unknown, + path = Array.of(), +): true | ValidationError[] { if (!isObject(u)) return [NULLARY.record(u, path)] let errors = Array.of() let keys = Object_keys(u) @@ -122,9 +149,12 @@ function validateRecord(this: t.record, u: unknown, path return errors.length === 0 || errors } -// validateUnion.optional = 0 -validateUnion.tag = URI.union -function validateUnion(this: t.union, u: unknown, path: (keyof any)[] = []) { +function validateUnion( + this: t.union<{ [I in keyof T]: Validator }>, + u: T[number] | Unknown, + path = Array.of(), +): true | ValidationError[] { + // validateUnion.optional = 0 // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; let errors = Array.of() for (let i = 0; i < this.def.length; i++) { @@ -139,8 +169,11 @@ function validateUnion(this: t.union, u: unknown, path: (keyof any) return errors.length === 0 || errors } -validateIntersect.tag = URI.intersect -function validateIntersect(this: t.intersect, u: unknown, path: (keyof any)[] = []) { +function validateIntersect( + this: t.intersect, + u: unknown, + path = Array.of(), +): true | ValidationError[] { let errors = Array.of() for (let i = 0; i < this.def.length; i++) { let results = this.def[i].validate(u, path) @@ -150,8 +183,12 @@ function validateIntersect(this: t.intersect, u: unknown, return errors.length === 0 || errors } -validateTuple.tag = URI.tuple -function validateTuple(this: t.tuple, u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + +function validateTuple( + this: t.tuple, + u: unknown, + path = Array.of(), +): true | ValidationError[] { let errors = Array.of() if (!Array_isArray(u)) return [ERROR.array(u, path)] for (let i = 0; i < this.def.length; i++) { @@ -174,8 +211,11 @@ function validateTuple(this: t.tuple, u: unknown, path: (k return errors.length === 0 || errors } -validateObject.tag = URI.object -function validateObject(this: t.object<{ [x: string]: Validator }>, u: unknown, path: (keyof any)[] = []): true | ValidationError[] { +function validateObject( + this: t.object<{ [x: string]: Validator }>, + u: unknown, + path = Array.of(), +): true | ValidationError[] { if (!isObject(u)) return [ERROR.object(u, path)] let errors = Array.of() let { schema: { optionalTreatment } } = getConfig() @@ -184,7 +224,7 @@ function validateObject(this: t.object<{ [x: string]: Validator }>, u: unknown, for (let i = 0, len = keys.length; i < len; i++) { let k = keys[i] let path_ = [...path, k] - if (hasOwn(u, k) && u[k] === undefined) { + if (Object_hasOwn(u, k) && u[k] === undefined) { if (t.optional.is(this.def[k].validate)) { let tag = typeName(this.def[k].validate) if (isKeyOf(tag, NULLARY)) { @@ -206,7 +246,7 @@ function validateObject(this: t.object<{ [x: string]: Validator }>, u: unknown, } errors.push(...results) } - else if (hasOwn(u, k)) { + else if (Object_hasOwn(u, k)) { let results = this.def[k].validate(u[k], path_) if (results === true) continue errors.push(...results) @@ -218,18 +258,17 @@ function validateObject(this: t.object<{ [x: string]: Validator }>, u: unknown, } } else { - // else if (optionalTreatment === 'presentButUndefinedIsOK') { for (let i = 0, len = keys.length; i < len; i++) { let k = keys[i] let path_ = [...path, k] - if (!hasOwn(u, k)) { + if (!Object_hasOwn(u, k)) { if (!t.optional.is(this.def[k].validate)) { errors.push(UNARY.object.missing(u, path_)) continue } else { - if (!hasOwn(u, k)) continue - if (t.optional.is(this.def[k].validate) && hasOwn(u, k)) { + if (!Object_hasOwn(u, k)) continue + if (t.optional.is(this.def[k].validate) && Object_hasOwn(u, k)) { if (u[k] === undefined) continue let results = this.def[k].validate(u[k], path_) if (results === true) continue diff --git a/packages/derive-validators/test/bind.test.ts b/packages/derive-validators/test/bind.test.ts index a6c6b9ed..40ed2263 100644 --- a/packages/derive-validators/test/bind.test.ts +++ b/packages/derive-validators/test/bind.test.ts @@ -1,5 +1,5 @@ import * as vi from 'vitest' -import { t, configure } from '@traversable/schema' +import { t, configure } from '@traversable/schema-core' import '@traversable/derive-validators/install' vi.describe('〖⛳️〗‹‹‹ ❲@traversable/validation❳', () => { diff --git a/packages/derive-validators/test/install.test.ts b/packages/derive-validators/test/install.test.ts index 1d1b607c..aba12084 100644 --- a/packages/derive-validators/test/install.test.ts +++ b/packages/derive-validators/test/install.test.ts @@ -1,5 +1,5 @@ import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' vi.describe('〖⛳️〗‹‹‹ ❲@traversable/derive-validators❳', () => { vi.it('〖⛳️〗› ❲pre-install❳', () => vi.assert.isFalse(t.has('validate')(t.string))) diff --git a/packages/derive-validators/test/validators.test.ts b/packages/derive-validators/test/validators.test.ts index 1da9b7a5..5ad54046 100644 --- a/packages/derive-validators/test/validators.test.ts +++ b/packages/derive-validators/test/validators.test.ts @@ -3,7 +3,7 @@ import { fc, test } from '@fast-check/vitest' import { Seed } from '@traversable/schema-seed' import { symbol } from '@traversable/registry' -import { t, configure } from '@traversable/schema' +import { t, configure } from '@traversable/schema-core' import { dataPathFromSchemaPath as dataPath, fromSchema } from '@traversable/derive-validators' import '@traversable/derive-validators/install' diff --git a/packages/derive-validators/tsconfig.build.json b/packages/derive-validators/tsconfig.build.json index e36720e1..7af09c34 100644 --- a/packages/derive-validators/tsconfig.build.json +++ b/packages/derive-validators/tsconfig.build.json @@ -10,6 +10,6 @@ "references": [ { "path": "../json" }, { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ] } diff --git a/packages/derive-validators/tsconfig.src.json b/packages/derive-validators/tsconfig.src.json index 076cde65..a021e790 100644 --- a/packages/derive-validators/tsconfig.src.json +++ b/packages/derive-validators/tsconfig.src.json @@ -9,7 +9,7 @@ "references": [ { "path": "../json" }, { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ], "include": ["src"] } diff --git a/packages/derive-validators/tsconfig.test.json b/packages/derive-validators/tsconfig.test.json index 3cfc5619..e91761f8 100644 --- a/packages/derive-validators/tsconfig.test.json +++ b/packages/derive-validators/tsconfig.test.json @@ -10,7 +10,7 @@ { "path": "tsconfig.src.json" }, { "path": "../json" }, { "path": "../registry" }, - { "path": "../schema" }, + { "path": "../schema-core" }, { "path": "../schema-seed" }, ], "include": ["test"] diff --git a/packages/registry/src/equals.ts b/packages/registry/src/equals.ts index 7a668c79..77cbf231 100644 --- a/packages/registry/src/equals.ts +++ b/packages/registry/src/equals.ts @@ -39,7 +39,7 @@ export const IsStrictlyEqual = (l: T, r: T): boolean => l === r /** * ## {@link SameValueNumber `Equal.SameValueNumber`} * - * Specified by TC39's + * TC39-compliant implementation of * [`Number::sameValue`](https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-numeric-types-number-sameValue) */ export const SameValueNumber = ( @@ -53,7 +53,7 @@ export const SameValueNumber = ( /** * ## {@link SameValue `Equal.SameValue`} * - * Specified by TC39's + * TC39-compliant implementation of * [`SameValue`](https://tc39.es/ecma262/multipage/abstract-operations.html#sec-samevalue) */ export const SameValue diff --git a/packages/registry/src/exports.ts b/packages/registry/src/exports.ts index 94d09bd3..7fd28c65 100644 --- a/packages/registry/src/exports.ts +++ b/packages/registry/src/exports.ts @@ -29,6 +29,14 @@ export { parseKey, } from './parse.js' export * as Equal from './equals.js' +export { + IsStrictlyEqual, + SameType, + SameValue, + SameValueNumber, + deep as deepEquals, + lax as laxEquals, +} from './equals.js' export type Equal = import('./types.js').Equal export type { GlobalOptions, OptionalTreatment, SchemaOptions } from './options.js' @@ -64,3 +72,5 @@ export { export { merge, mut } from './merge.js' export { ValueSet } from './set.js' + +export { isPredicate } from './predicate.js' diff --git a/packages/registry/src/globalThis.ts b/packages/registry/src/globalThis.ts index 4253af3d..4631ebe2 100644 --- a/packages/registry/src/globalThis.ts +++ b/packages/registry/src/globalThis.ts @@ -18,18 +18,19 @@ export const Array_isArray : (u: unknown) => u is T[] = globalThis.Array.isArray -export const Math_max = globalThis.Math.min +export const Math_max = globalThis.Math.max export const Math_min = globalThis.Math.min export const Number_isInteger : (x: unknown) => x is number - = globalThis.Number.isInteger as never + = globalThis.Number.isInteger export const Number_isSafeInteger : (x: unknown) => x is number - = globalThis.Number.isSafeInteger as never + = globalThis.Number.isSafeInteger export const Object_assign = globalThis.Object.assign +export const Object_defineProperty = globalThis.Object.defineProperty export const Object_is = globalThis.Object.is export const Object_values = globalThis.Object.values @@ -41,6 +42,12 @@ export const Object_keys : (x: T) => (K)[] = globalThis.Object.keys +export const Object_getOwnPropertySymbols: { + (x: T): (K)[] + (x: {}): symbol[] + (x: T): (K)[] +} = globalThis.Object.getOwnPropertySymbols + export type Object_fromEntries = never | Force< & { [E in Entry.Optional as E[0]]+?: E[1] } & { [E in Entry.Required as E[0]]-?: E[1] } diff --git a/packages/registry/src/parseArgs.ts b/packages/registry/src/parseArgs.ts index f9eeae8c..509041fe 100644 --- a/packages/registry/src/parseArgs.ts +++ b/packages/registry/src/parseArgs.ts @@ -1,6 +1,6 @@ /** * TODO: Currently this is hardcoded to avoid creating a dependency on - * `@traversable/schema`. Figure out a better way to handle this. + * `@traversable/schema-core`. Figure out a better way to handle this. */ import type { Equal } from "./types.js" diff --git a/packages/registry/src/predicate.ts b/packages/registry/src/predicate.ts new file mode 100644 index 00000000..1c1f24f4 --- /dev/null +++ b/packages/registry/src/predicate.ts @@ -0,0 +1,4 @@ + +export const isPredicate + : (src: unknown) => src is { (): boolean; (x: S): x is T } + = (src: unknown): src is never => typeof src === 'function' diff --git a/packages/registry/src/satisfies.ts b/packages/registry/src/satisfies.ts index ab2e230e..65af329a 100644 --- a/packages/registry/src/satisfies.ts +++ b/packages/registry/src/satisfies.ts @@ -62,6 +62,13 @@ export type NonUnion< = ([T] extends [infer _] ? _ : never) > = _ extends _ ? [T] extends [_] ? _ : never : never +export type OnlyUnion< + T, + _ extends + | ([T] extends [infer _] ? _ : never) + = ([T] extends [infer _] ? _ : never) +> = _ extends _ ? [T] extends [_] ? never : _ : never + export type NonFiniteArray = [T] extends [readonly unknown[]] ? number extends T['length'] diff --git a/packages/registry/src/uri.ts b/packages/registry/src/uri.ts index a4dee856..a97afdb5 100644 --- a/packages/registry/src/uri.ts +++ b/packages/registry/src/uri.ts @@ -37,7 +37,7 @@ export { URI_typeclass as typeclass, } -export const SCOPE = '@traversable/schema/URI' +export const SCOPE = '@traversable/schema-core/URI' export const NS = `${SCOPE}::` export type NS = typeof NS diff --git a/packages/schema/CHANGELOG.md b/packages/schema-core/CHANGELOG.md similarity index 100% rename from packages/schema/CHANGELOG.md rename to packages/schema-core/CHANGELOG.md diff --git a/packages/schema/README.md b/packages/schema-core/README.md similarity index 91% rename from packages/schema/README.md rename to packages/schema-core/README.md index 61df6ae0..63da4390 100644 --- a/packages/schema/README.md +++ b/packages/schema-core/README.md @@ -1,5 +1,5 @@
-

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘀𝗰𝗵𝗲𝗺𝗮

+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘀𝗰𝗵𝗲𝗺𝗮-𝗰𝗼𝗿𝗲


@@ -10,18 +10,18 @@

- NPM Version + NPM Version   TypeScript   Static Badge   - npm + npm  
- npm bundle size (scoped) + npm bundle size (scoped)   Static Badge   @@ -34,14 +34,14 @@   •   TypeScript Playground   •   - npm + npm


-`@traversable/schema` exploits a TypeScript feature called +`@traversable/schema-core` exploits a TypeScript feature called [inferred type predicates](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#inferred-type-predicates) to do what libaries like `zod` do, without the additional runtime overhead or abstraction. @@ -55,14 +55,14 @@ to do what libaries like `zod` do, without the additional runtime overhead or ab ## Requirements The only hard requirement is [TypeScript 5.5](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/). -Since the core primitive that `@traversable/schema` is built on top of is +Since the core primitive that `@traversable/schema-core` is built on top of is [inferred type predicates](https://devblogs.microsoft.com/typescript/announcing-typescript-5-5/#inferred-type-predicates), we do not have plans to backport to previous versions. ## Quick start ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' declare let ex_01: unknown @@ -90,7 +90,7 @@ if (schema_01(ex_01)) { ## Features -`@traversable/schema` is modular by schema (like valibot), but takes it a step further by making its feature set opt-in by default. +`@traversable/schema-core` is modular by schema (like valibot), but takes it a step further by making its feature set opt-in by default. The ability to add features like this is a knock-on effect of traversable's extensible core. @@ -104,14 +104,14 @@ which no other schema library currently does (although please file an issue if t This is possible because the traversable schemas are themselves just type predicates with a few additional properties that allow them to also be used for reflection. -- **Instructions:** To use this feature, define a predicate inline and `@traversable/schema` will figure out the rest. +- **Instructions:** To use this feature, define a predicate inline and `@traversable/schema-core` will figure out the rest. #### Example You can play with this example in the TypeScript Playground. ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export let Classes = t.object({ promise: (v) => v instanceof Promise, @@ -195,7 +195,7 @@ type Shorthand = t.typeof Play with this example in the [TypeScript playground](https://tsplay.dev/NaBEPm). ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import '@traversable/derive-validators/install' // ↑↑ importing `@traversable/derive-validators/install` adds `.validate` to all schemas @@ -241,7 +241,7 @@ keys are printed at runtime might differ from the order they appear on the type- Play with this example in the [TypeScript playground](https://tsplay.dev/W49jew) ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import '@traversable/schema-to-string/install' // ↑↑ importing `@traversable/schema-to-string/install` adds the upgraded `.toString` method on all schemas @@ -284,7 +284,7 @@ Play with this example in the [TypeScript playground](https://tsplay.dev/NB98Vw) ```typescript import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import '@traversable/schema-to-json-schema/install' // ↑↑ importing `@traversable/schema-to-json-schema/install` adds `.toJsonSchema` on all schemas @@ -348,22 +348,21 @@ vi.assertType<{ ### Codec (`.pipe`, `.extend`, `.parse`, `.decode` & `.encode`) -- **Instructions:** to install the `.codec` method on all schemas, all you need to do is import `@traversable/derive-codec`. - - To create a covariant codec (similar to zod's `.transform`), use `.codec.pipe` - - To create a contravariant codec (similar to zod's `.preprocess`), use `.codec.extend` (WIP) +- **Instructions:** to install the `.pipe` and `.extend` methods on all schemas, simply `@traversable/derive-codec/install`. + - To create a covariant codec (similar to zod's `.transform`), use `.pipe` + - To create a contravariant codec (similar to zod's `.preprocess`), use `.extend` (WIP) #### Example Play with this example in the [TypeScript playground](https://tsplay.dev/mbbv3m). ```typescript -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import '@traversable/derive-codec/install' -// ↑↑ importing `@traversable/derive-codec/install` adds `.codec` on all schemas +// ↑↑ importing `@traversable/derive-codec/install` adds `.pipe` and `.extend` on all schemas let User = t .object({ name: t.optional(t.string), createdAt: t.string }) - .codec // <-- notice we're pulling off the `.codec` property .pipe((user) => ({ ...user, createdAt: new Date(user.createdAt) })) .unpipe((user) => ({ ...user, createdAt: user.createdAt.toISOString() })) diff --git a/packages/schema/package.json b/packages/schema-core/package.json similarity index 96% rename from packages/schema/package.json rename to packages/schema-core/package.json index f2414b22..072c8663 100644 --- a/packages/schema/package.json +++ b/packages/schema-core/package.json @@ -1,7 +1,7 @@ { - "name": "@traversable/schema", + "name": "@traversable/schema-core", "type": "module", - "version": "0.0.35", + "version": "0.0.0", "private": false, "description": "", "license": "MIT", diff --git a/packages/schema/src/__generated__/__manifest__.ts b/packages/schema-core/src/__generated__/__manifest__.ts similarity index 96% rename from packages/schema/src/__generated__/__manifest__.ts rename to packages/schema-core/src/__generated__/__manifest__.ts index 5ff492d9..d6977800 100644 --- a/packages/schema/src/__generated__/__manifest__.ts +++ b/packages/schema-core/src/__generated__/__manifest__.ts @@ -1,7 +1,7 @@ export default { - "name": "@traversable/schema", + "name": "@traversable/schema-core", "type": "module", - "version": "0.0.35", + "version": "0.0.0", "private": false, "description": "", "license": "MIT", diff --git a/packages/schema/src/schema.ts b/packages/schema-core/src/__schema.ts__ similarity index 83% rename from packages/schema/src/schema.ts rename to packages/schema-core/src/__schema.ts__ index 7da93591..565fd0b1 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema-core/src/__schema.ts__ @@ -1,5 +1,6 @@ import type { Array, + Force, Functor as Functor_, HKT, Mut, @@ -11,10 +12,16 @@ import type { import { applyOptions, + Array_isArray, fn, getConfig, has, + isPredicate, + Math_min, + Math_max, Number_isSafeInteger, + Object_assign, + Object_keys, parseArgs, safeCoerce, symbol, @@ -22,88 +29,27 @@ import { } from '@traversable/registry' import type { + Entry, + FirstOptionalItem, Guard, - Label, - Predicate as AnyPredicate, + Guarded, + IntersectType, + invalid, + Optional, + Required, + Schema, + SchemaLike, + TupleType, Typeguard, - TypePredicate, ValidateTuple, } from './types.js' -import { is as guard } from './predicates.js' -import type { Bounds } from './bounded.js' -import { within, withinBig } from './bounded.js' - -/** @internal */ -const Object_assign = globalThis.Object.assign - -/** @internal */ -const Object_keys = globalThis.Object.keys - -/** @internal */ -const Array_isArray = globalThis.Array.isArray - - -/** @internal */ -const Math_min = globalThis.Math.min -/** @internal */ -const Math_max = globalThis.Math.max - -/** @internal */ -export function carryover(x: T, ...ignoreKeys: (keyof T)[]) { - let keys = Object.keys(x).filter((k) => !ignoreKeys.includes(k as never) && x[k as keyof typeof x] != null) - if (keys.length === 0) return {} - else { - let out: { [x: string]: unknown } = {} - for (let k of keys) out[k] = x[k as keyof typeof x] - return out - } -} +import type { Bounds } from './bounded.js' +import { carryover, within, withinBig } from './bounded.js' +import { is as guard } from './predicates.js' +import { optional } from './optional.js' -export const isPredicate - : (src: unknown) => src is { (): boolean; (x: S): x is T } - = (src: unknown): src is never => typeof src === 'function' - -export type Source = T extends (_: infer S) => unknown ? S : unknown -export type Target = never | S extends (_: any) => _ is infer T ? T : S -export type Inline = never | of> -export type Predicate = AnyPredicate | Schema -export type Force = never | { -readonly [K in keyof T]: T[K] } -export type Optional = never | - string extends K ? string : K extends K ? S[K] extends bottom | optional ? K : never : never -export type FirstOptionalItem - = S extends readonly [infer H, ...infer T] ? optional extends H ? Offset['length'] : FirstOptionalItem : never - ; - -export type Required = never | - string extends K ? string : K extends K ? S[K] extends bottom | optional ? never : K : never -export type Entry - = [Schema] extends [S] ? Schema - : S extends { def: unknown } ? S - : S extends Guard ? of - : S extends globalThis.BooleanConstructor ? nonnullable - : S extends (() => infer _ extends boolean) - ? BoolLookup[`${_}`] - : S -export type BoolLookup = never | { - true: top - false: bottom - boolean: unknown_ -} - -export type IntersectType - = Todo extends readonly [infer H, ...infer T] ? IntersectType : Out -export type TupleType = never - | optional extends T[number & keyof T] - ? T extends readonly [infer Head, ...infer Tail] - ? [Head] extends [optional] ? Label< - { [ix in keyof Out]: Out[ix]['_type' & keyof Out[ix]] }, - { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } - > - : TupleType - : never - : { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } - ; +export type Inline = never | of> export type typeOf< T extends { _type?: unknown }, @@ -112,23 +58,7 @@ export type typeOf< = T['_type'] > = never | _ -export interface Unspecified extends LowerBound { } -export interface LowerBound { - (u: unknown): u is any - tag?: string - def?: unknown - _type?: T -} - -export interface Schema - extends TypePredicate, Fn['_type']> { - tag?: Fn['tag'] - def?: Fn['def'] - _type?: Fn['_type'] -} - export type Unary = - // | enum_ | eq | array | record @@ -141,7 +71,6 @@ export type Unary = export type F = | Leaf - // | enum_ | eq | array | record @@ -157,7 +86,7 @@ export type Fixpoint = export interface Free extends HKT { [-1]: F } -export function of(typeguard: S): Entry +export function of(typeguard: S): Entry export function of(typeguard: S): of export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { typeguard.def = typeguard @@ -165,14 +94,13 @@ export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard } export interface of { (u: unknown): u is this['_type'] - _type: Target + _type: Guarded tag: URI.inline def: S } export namespace of { export let prototype = { tag: URI.inline } - export type type> = never | T export function def(guard: T): of /* v8 ignore next 6 */ export function def(guard: T) { @@ -183,10 +111,6 @@ export namespace of { } } -export interface top { tag: URI.top, readonly _type: unknown, def: this['_type'] } -export interface bottom { tag: URI.bottom, readonly _type: never, def: this['_type'] } -export interface invalid<_Err> extends TypeError<''>, never_ { } - export { void_ as void, void_ } interface void_ extends Typeguard { tag: URI.void, def: this['_type'] } const void_ = function VoidSchema(src: unknown) { return src === void 0 } @@ -513,37 +437,37 @@ export namespace eq { } } -export function optional(schema: S): optional -export function optional(schema: S): optional> -export function optional(schema: S): optional { return optional.def(schema) } -export interface optional { - tag: URI.optional - def: S - [symbol.optional]: number - _type: undefined | S['_type' & keyof S] - (u: unknown): u is this['_type'] -} -export namespace optional { - export let prototype = { tag: URI.optional } - export type type = never | T - export function def(x: T): optional - export function def(x: T) { - const optionalGuard = isPredicate(x) ? guard.optional(x) : (_: any) => true - function OptionalSchema(src: unknown) { return optionalGuard(src) } - OptionalSchema.tag = URI.optional - OptionalSchema.def = x - OptionalSchema[symbol.optional] = 1 - return Object_assign(OptionalSchema, optional.prototype) - } - export const is - : (u: unknown) => u is optional - /* v8 ignore next 1 */ - = has('tag', eq(URI.optional)) as never -} +// export function optional(schema: S): optional +// export function optional(schema: S): optional> +// export function optional(schema: S): optional { return optional.def(schema) } +// export interface optional { +// tag: URI.optional +// def: S +// [symbol.optional]: number +// _type: undefined | S['_type' & keyof S] +// (u: unknown): u is this['_type'] +// } +// export namespace optional { +// export let prototype = { tag: URI.optional } +// export type type = never | T +// export function def(x: T): optional +// export function def(x: T) { +// const optionalGuard = isPredicate(x) ? guard.optional(x) : (_: any) => true +// function OptionalSchema(src: unknown) { return optionalGuard(src) } +// OptionalSchema.tag = URI.optional +// OptionalSchema.def = x +// OptionalSchema[symbol.optional] = 1 +// return Object_assign(OptionalSchema, optional.prototype) +// } +// export const is +// : (u: unknown) => u is optional +// /* v8 ignore next 1 */ +// = has('tag', eq(URI.optional)) as never +// } export function array(schema: S, readonly: 'readonly'): readonlyArray export function array(schema: S): array -export function array(schema: S): array> +export function array(schema: S): array> export function array(schema: S): array { return array.def(schema) } export interface array extends array.methods { (u: unknown): u is this['_type'] @@ -616,7 +540,7 @@ export namespace array { export const readonlyArray: { (schema: S, readonly: 'readonly'): readonlyArray - (schema: S): readonlyArray> + (schema: S): readonlyArray> } = array export interface readonlyArray { (u: unknown): u is this['_type'] @@ -626,7 +550,7 @@ export interface readonlyArray { } export function record(schema: S): record -export function record(schema: S): record> +export function record(schema: S): record> export function record(schema: S) { return record.def(schema) } export interface record { (u: unknown): u is this['_type'] @@ -648,8 +572,8 @@ export namespace record { } export function union(...schemas: S): union -export function union }>(...schemas: S): union -export function union(...schemas: S): {} { return union.def(schemas) } +export function union }>(...schemas: S): union +export function union(...schemas: S): {} { return union.def(schemas) } export interface union { (u: unknown): u is this['_type'] tag: URI.union @@ -670,7 +594,7 @@ export namespace union { } export function intersect(...schemas: S): intersect -export function intersect }>(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect export function intersect(...schemas: S) { return intersect.def(schemas) } export interface intersect { (u: unknown): u is this['_type'], tag: URI.intersect, def: S, _type: IntersectType } export namespace intersect { @@ -687,13 +611,13 @@ export namespace intersect { } export { tuple } -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple>(...schemas: tuple.validate): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> -function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> function tuple(...schemas: tuple.validate): tuple, S>> -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...args: | [...S] | [...S, Options]) { return tuple.def(...parseArgs(getConfig().schema, args)) } +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...args: | [...S] | [...S, Options]) { return tuple.def(...parseArgs(getConfig().schema, args)) } interface tuple { (u: unknown): u is this['_type'], tag: URI.tuple, def: S, _type: TupleType, opt: FirstOptionalItem } namespace tuple { export let prototype = { tag: URI.tuple } as tuple @@ -712,6 +636,7 @@ namespace tuple { return Object_assign(TupleSchema, tuple.prototype) } } + declare namespace tuple { type validate = ValidateTuple> type from @@ -725,7 +650,7 @@ function object_< T extends { [K in keyof S]: Entry } >(schemas: S, options?: Options): object_ function object_< - S extends { [x: string]: Predicate }, + S extends { [x: string]: SchemaLike }, T extends { [K in keyof S]: Entry } >(schemas: S, options?: Options): object_ // diff --git a/packages/schema-core/src/any.ts b/packages/schema-core/src/any.ts new file mode 100644 index 00000000..877f92af --- /dev/null +++ b/packages/schema-core/src/any.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { any_ as any } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface any_ extends any_.core { + //<%= Types %> +} + +function AnySchema(src: unknown): src is any { return true } +AnySchema.tag = URI.any +AnySchema.def = void 0 as any + +const any_ = Object_assign( + AnySchema, + userDefinitions, +) as any_ + +Object_assign(any_, userExtensions) + +declare namespace any_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.any + _type: any + get def(): this['_type'] + } +} diff --git a/packages/schema-core/src/array.ts b/packages/schema-core/src/array.ts new file mode 100644 index 00000000..99b7e174 --- /dev/null +++ b/packages/schema-core/src/array.ts @@ -0,0 +1,117 @@ +import type { Integer, Unknown } from '@traversable/registry' +import { + Array_isArray, + bindUserExtensions, + isPredicate, + has, + Math_max, + Math_min, + Number_isSafeInteger, + Object_assign, + safeCoerce, + URI, +} from '@traversable/registry' + +import type { Guarded, Schema, SchemaLike } from './types.js' +import type { of } from './of.js' +import type { Bounds } from './bounded.js' +import type { readonlyArray } from './readonlyArray.js' +import { carryover, within } from './bounded.js' + +/** @internal */ +function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { + return Object_assign(function BoundedArraySchema(u: unknown) { + return Array_isArray(u) && within(bounds)(u.length) + }, carry, array(schema)) +} + +export interface array extends array.core { + //<%= Types %> +} + +export function array(schema: S, readonly: 'readonly'): readonlyArray +export function array(schema: S): array +export function array(schema: S): array>> +export function array(schema: S): array { + return array.def(schema) +} + +export namespace array { + export let userDefinitions: Record = { + //<%= Definitions %> + } as array + export function def(x: S, prev?: array): array + export function def(x: S, prev?: unknown): array + export function def(x: S, prev?: array): array + /* v8 ignore next 1 */ + export function def(x: unknown, prev?: unknown): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const arrayPredicate = isPredicate(x) ? array$(safeCoerce(x)) : Array_isArray + function ArraySchema(src: unknown) { return arrayPredicate(src) } + ArraySchema.tag = URI.array + ArraySchema.def = x + ArraySchema.min = function arrayMin(minLength: Min) { + return Object_assign( + boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), + { minLength }, + ) + } + ArraySchema.max = function arrayMax(maxLength: Max) { + return Object_assign( + boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), + { maxLength }, + ) + } + ArraySchema.between = function arrayBetween( + min: Min, + max: Max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max) + ) { + return Object_assign( + boundedArray(x, { gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) + } + if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength + if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength + Object_assign(ArraySchema, userDefinitions) + return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) + } +} + +let array$ + : (f: (u: unknown) => u is unknown) => (u: unknown) => u is unknown[] + = (f: (u: unknown) => u is unknown) => (u: unknown): u is unknown[] => Array_isArray(u) && u.every(f) + +export declare namespace array { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.array + get def(): S + _type: S['_type' & keyof S][] + minLength?: number + maxLength?: number + min>(minLength: Min): array.Min + max>(maxLength: Max): array.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> + } + type Min + = [Self] extends [{ maxLength: number }] + ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> + : array.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> + : array.max + ; + interface min extends array { minLength: Min } + interface max extends array { maxLength: Max } + interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } + type type = never | T +} diff --git a/packages/schema-core/src/bigint.ts b/packages/schema-core/src/bigint.ts new file mode 100644 index 00000000..cd57891d --- /dev/null +++ b/packages/schema-core/src/bigint.ts @@ -0,0 +1,96 @@ +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, Object_assign, URI } from '@traversable/registry' + +import type { Bounds } from './bounded.js' +import { carryover, withinBig as within } from './bounded.js' + +export { bigint_ as bigint } + +/** @internal */ +function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedBigIntSchema(u: unknown) { + return bigint_(u) && within(bounds)(u) + }, carry, bigint_) +} + +interface bigint_ extends bigint_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function BigIntSchema(src: unknown) { return typeof src === 'bigint' } +BigIntSchema.tag = URI.bigint +BigIntSchema.def = 0n + +const bigint_ = Object_assign( + BigIntSchema, + userDefinitions, +) as bigint_ + +bigint_.min = function bigIntMin(minimum) { + return Object_assign( + boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +bigint_.max = function bigIntMax(maximum) { + return Object_assign( + boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +bigint_.between = function bigIntBetween( + min, + max, + minimum = (max < min ? max : min), + maximum = (max < min ? min : max), +) { + return Object_assign( + boundedBigInt({ gte: minimum, lte: maximum }), + { minimum, maximum } + ) +} + +Object_assign( + bigint_, + bindUserExtensions(bigint_, userExtensions), +) + +declare namespace bigint_ { + interface core extends bigint_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: bigint + tag: URI.bigint + get def(): this['_type'] + minimum?: bigint + maximum?: bigint + } + type Min + = [Self] extends [{ maximum: bigint }] + ? bigint_.between<[min: X, max: Self['maximum']]> + : bigint_.min + ; + type Max + = [Self] extends [{ minimum: bigint }] + ? bigint_.between<[min: Self['minimum'], max: X]> + : bigint_.max + ; + interface methods { + min(minimum: Min): bigint_.Min + max(maximum: Max): bigint_.Max + between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> + } + interface min extends bigint_ { minimum: Min } + interface max extends bigint_ { maximum: Max } + interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } +} + diff --git a/packages/schema-core/src/boolean.ts b/packages/schema-core/src/boolean.ts new file mode 100644 index 00000000..c89adf4b --- /dev/null +++ b/packages/schema-core/src/boolean.ts @@ -0,0 +1,37 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { boolean_ as boolean } + +interface boolean_ extends boolean_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } + +BooleanSchema.tag = URI.boolean +BooleanSchema.def = false + +const boolean_ = Object_assign( + BooleanSchema, + userDefinitions, +) as boolean_ + +Object_assign(boolean_, userExtensions) + +declare namespace boolean_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.boolean + _type: boolean + get def(): this['_type'] + } +} diff --git a/packages/schema/src/bounded.ts b/packages/schema-core/src/bounded.ts similarity index 79% rename from packages/schema/src/bounded.ts rename to packages/schema-core/src/bounded.ts index 33fd1828..4ef69e98 100644 --- a/packages/schema/src/bounded.ts +++ b/packages/schema-core/src/bounded.ts @@ -36,3 +36,13 @@ export function withinBig({ gte = void 0, lte = void 0, gt, lt }: Bounds(x: T, ...ignoreKeys: (keyof T)[]) { + let keys = Object.keys(x).filter((k) => !ignoreKeys.includes(k as never) && x[k as keyof typeof x] != null) + if (keys.length === 0) return {} + else { + let out: { [x: string]: unknown } = {} + for (let k of keys) out[k] = x[k as keyof typeof x] + return out + } +} diff --git a/packages/schema-core/src/clone.ts b/packages/schema-core/src/clone.ts new file mode 100644 index 00000000..203c416b --- /dev/null +++ b/packages/schema-core/src/clone.ts @@ -0,0 +1,9 @@ +import type { Param } from '@traversable/registry' +import type { LowerBound } from './types.js' + +export function clone(schema: S): S +export function clone(schema: S) { + function cloned(u: Param) { return schema(u) } + for (const k in schema) (cloned as typeof schema)[k] = schema[k] + return cloned +} diff --git a/packages/schema/src/combinators.ts b/packages/schema-core/src/combinators.ts similarity index 74% rename from packages/schema/src/combinators.ts rename to packages/schema-core/src/combinators.ts index dafb688b..a86075f8 100644 --- a/packages/schema/src/combinators.ts +++ b/packages/schema-core/src/combinators.ts @@ -1,18 +1,19 @@ import type * as T from './types.js' -import * as t from './schema.js' +import { LowerBound, Schema, SchemaLike } from './types.js' +import type { of } from './of.js' -type Predicate = T.Predicate | t.Schema /** * ## {@link filter `t.filter`} */ -export function filter, T extends t.LowerBound>(schema: S, filter: T): T -export function filter, T extends S['_type']>(schema: S, filter: (s: S['_type']) => s is T): t.of -export function filter(schema: S, filter: (s: S['_type']) => boolean): S +export function filter>(schema: S, filter: T): T +export function filter(schema: S, filter: (s: S['_type']) => s is T): of +export function filter(schema: S, filter: (s: S['_type']) => boolean): S export function filter(guard: T.Guard, narrower: (x: T) => x is U): T.Guard +export function filter(guard: S): (predicate: (x: S['_type']) => boolean) => S export function filter(guard: T.Guard, predicate: (x: T) => boolean): T.Guard export function filter(guard: T.Guard): (predicate: (x: T) => boolean) => T.Guard -export function filter(...args: [guard: T.Guard] | [guard: T.Guard, predicate: T.Predicate]) { +export function filter(...args: [any] | [any, any]) { if (args.length === 1) return (predicate: T.Predicate) => filter(args[0], predicate) else return (x: T) => args[0](x) && args[1](x) } diff --git a/packages/schema-core/src/core.ts b/packages/schema-core/src/core.ts new file mode 100644 index 00000000..88f31e6b --- /dev/null +++ b/packages/schema-core/src/core.ts @@ -0,0 +1,142 @@ +import type { HKT, Functor as Functor_ } from '@traversable/registry' +import { fn, has, Object_assign, symbol, URI } from '@traversable/registry' + +import type { Guarded, Schema } from './types.js' + +import type { of } from './of.js' +import { never as never_ } from './never.js' +import { any as any_ } from './any.js' +import { unknown as unknown_ } from './unknown.js' +import { void as void_ } from './void.js' +import { null as null_ } from './null.js' +import { undefined as undefined_ } from './undefined.js' +import { symbol as symbol_ } from './symbol.js' +import { boolean as boolean_ } from './boolean.js' +import { integer as integer } from './integer.js' +import { bigint as bigint_ } from './bigint.js' +import { number as number_ } from './number.js' +import { string as string_ } from './string.js' +import { eq } from './eq.js' +import { optional } from './optional.js' +import { array } from './array.js' +import { record } from './record.js' +import { union } from './union.js' +import { intersect } from './intersect.js' +import { tuple } from './tuple.js' +import { object as object_ } from './object.js' + +export type Inline = never | of> + +export type typeOf< + T extends { _type?: unknown }, + _ extends + | T['_type'] + = T['_type'] +> = never | _ + +export type Unary = + | eq + | array + | record + | optional + | union + | intersect + | tuple + | object_<{ [x: string]: Unary }> + + +export type F = + | Leaf + | eq + | array + | record + | optional + | union + | intersect + | tuple + | object_<{ [x: string]: T }> + +export type Fixpoint = + | Leaf + | Unary + +export interface Free extends HKT { [-1]: F } + +export type Leaf = typeof leaves[number] +export type LeafTag = Leaf['tag'] +export type Nullary = typeof nullaries[number] +export type NullaryTag = Nullary['tag'] +export type Boundable = typeof boundables[number] +export type BoundableTag = Boundable['tag'] +export type Tag = typeof tags[number] +export type UnaryTag = typeof unaryTags[number] +const hasTag = has('tag', (tag) => typeof tag === 'string') + +export const nullaries = [unknown_, never_, any_, void_, undefined_, null_, symbol_, boolean_] +export const nullaryTags = nullaries.map((x) => x.tag) +export const isNullaryTag = (u: unknown): u is NullaryTag => nullaryTags.includes(u as never) +export const isNullary = (u: unknown): u is Nullary => hasTag(u) && nullaryTags.includes(u.tag as never) + +export const boundables = [integer, bigint_, number_, string_] +export const boundableTags = boundables.map((x) => x.tag) +export const isBoundableTag = (u: unknown): u is BoundableTag => boundableTags.includes(u as never) +export const isBoundable = (u: unknown): u is Boundable => hasTag(u) && boundableTags.includes(u.tag as never) + +export const leaves = [...nullaries, ...boundables] +export const leafTags = leaves.map((leaf) => leaf.tag) +export const isLeaf = (u: unknown): u is Leaf => hasTag(u) && leafTags.includes(u.tag as never) + +export const unaryTags = [URI.optional, URI.eq, URI.array, URI.record, URI.tuple, URI.union, URI.intersect, URI.object] +export const tags = [...leafTags, ...unaryTags] +export const isUnary = (u: unknown): u is Unary => hasTag(u) && unaryTags.includes(u.tag as never) + +export const isCore: { + (u: F): u is F + (u: unknown): u is Fixpoint + (u: F): u is F +} = ((u: unknown) => hasTag(u) && tags.includes(u.tag as never)) as never + + +export declare namespace Functor { type Index = (keyof any)[] } +export const Functor: Functor_ = { + map(f) { + return (x) => { + switch (true) { + default: return fn.exhaustive(x) + case isLeaf(x): return x + case x.tag === URI.eq: return eq.def(x.def as never) as never + case x.tag === URI.array: return array.def(f(x.def), x) + case x.tag === URI.record: return record.def(f(x.def)) + case x.tag === URI.optional: return optional.def(f(x.def)) + case x.tag === URI.tuple: return tuple.def(fn.map(x.def, f)) + case x.tag === URI.object: return object_.def(fn.map(x.def, f)) + case x.tag === URI.union: return union.def(fn.map(x.def, f)) + case x.tag === URI.intersect: return intersect.def(fn.map(x.def, f)) + } + } + } +} + +export const IndexedFunctor: Functor_.Ix = { + ...Functor, + mapWithIndex(f) { + return (x, ix) => { + switch (true) { + default: return fn.exhaustive(x) + case isLeaf(x): return x + case x.tag === URI.eq: return eq.def(x.def as never) as never + case x.tag === URI.array: return array.def(f(x.def, ix), x) + case x.tag === URI.record: return record.def(f(x.def, ix)) + case x.tag === URI.optional: return optional.def(f(x.def, ix)) + case x.tag === URI.tuple: return tuple.def(fn.map(x.def, (y, iy) => f(y, [...ix, iy])), x.opt) + case x.tag === URI.object: return object_.def(fn.map(x.def, (y, iy) => f(y, [...ix, iy])), {}, x.opt as never) + case x.tag === URI.union: return union.def(fn.map(x.def, (y, iy) => f(y, [...ix, symbol.union, iy]))) + case x.tag === URI.intersect: return intersect.def(fn.map(x.def, (y, iy) => f(y, [...ix, symbol.intersect, iy]))) + } + } + } +} + +export const unfold = fn.ana(Functor) +export const fold = fn.cata(Functor) +export const foldWithIndex = fn.cataIx(IndexedFunctor) diff --git a/packages/schema/src/enum.ts b/packages/schema-core/src/enum.ts similarity index 90% rename from packages/schema/src/enum.ts rename to packages/schema-core/src/enum.ts index dc79d4fb..d0dc2f85 100644 --- a/packages/schema/src/enum.ts +++ b/packages/schema-core/src/enum.ts @@ -1,11 +1,5 @@ import type { Join, Primitive, Returns, Showable, UnionToTuple } from '@traversable/registry' -import { URI } from '@traversable/registry' - -/** @internal */ -const Object_values = globalThis.Object.values - -/** @internal */ -const Object_assign = globalThis.Object.assign +import { Object_assign, Object_values, URI } from '@traversable/registry' export type EnumType = T extends readonly unknown[] ? T[number] : T[keyof T] @@ -53,7 +47,7 @@ function enum_]>(...args: V return enum_.def(values) } namespace enum_ { - export let prototype = { tag: URI.enum } + export let userDefinitions = { tag: URI.enum } export function def(args: readonly [...T]): enum_ /* v8 ignore next 1 */ export function def(values: T): enum_ { @@ -64,6 +58,6 @@ namespace enum_ { enumGuard.get = values enumGuard.toJsonSchema = toJsonSchema enumGuard.toString = toString - return Object_assign(enumGuard, prototype) as never + return Object_assign(enumGuard, userDefinitions) as never } } diff --git a/packages/schema-core/src/eq.ts b/packages/schema-core/src/eq.ts new file mode 100644 index 00000000..d1c14d26 --- /dev/null +++ b/packages/schema-core/src/eq.ts @@ -0,0 +1,41 @@ +import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' +import { applyOptions, bindUserExtensions, isPredicate, Object_assign, URI } from '@traversable/registry' + +export function eq>(value: V, options?: Options): eq> +export function eq(value: V, options?: Options): eq +export function eq(value: V, options?: Options): eq { + return eq.def(value, options) +} + +export interface eq extends eq.core { + //<%= Types %> +} + +export namespace eq { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(value: T, options?: Options): eq + /* v8 ignore next 1 */ + export function def(x: T, $?: Options): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const options = applyOptions($) + const eqGuard = isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return eqGuard(src) } + EqSchema.tag = URI.eq + EqSchema.def = x + Object_assign(EqSchema, eq.userDefinitions) + return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) + } +} + +export declare namespace eq { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.eq + _type: V + get def(): V + } +} diff --git a/packages/schema/src/equals.ts b/packages/schema-core/src/equals.ts similarity index 100% rename from packages/schema/src/equals.ts rename to packages/schema-core/src/equals.ts diff --git a/packages/schema/src/exports.ts b/packages/schema-core/src/exports.ts similarity index 93% rename from packages/schema/src/exports.ts rename to packages/schema-core/src/exports.ts index b09b439d..70d4f7a1 100644 --- a/packages/schema/src/exports.ts +++ b/packages/schema-core/src/exports.ts @@ -46,10 +46,10 @@ export { defaults, getConfig, applyOptions, + symbol, } from '@traversable/registry' export * as t from './namespace.js' - export * from './extensions.js' export * as recurse from './recursive.js' @@ -59,15 +59,18 @@ export type Equal = import('@traversable/registry').Equal export * as Predicate from './predicates.js' export type Predicate = [T] extends [never] - ? import('./schema.js').Predicate + ? import('./types.js').SchemaLike : import('./types.js').Predicate export { clone } from './clone.js' export type { Bounds } from './bounded.js' export type { + FirstOptionalItem, Guard, + IntersectType, Label, + TupleType, Typeguard, ValidateTuple, } from './types.js' @@ -76,21 +79,13 @@ export { get, get$ } from './utils.js' export { VERSION } from './version.js' -export type { - FirstOptionalItem, - IntersectType, - TupleType, -} from './schema.js' -export { - /** @internal */ - carryover as __carryover, -} from './schema.js' - export { /** @internal */ within as __within, /** @internal */ withinBig as __withinBig, + /** @internal */ + carryover as __carryover, } from './bounded.js' export { /** @internal */ diff --git a/packages/schema-core/src/extensions.ts b/packages/schema-core/src/extensions.ts new file mode 100644 index 00000000..c61c6ae7 --- /dev/null +++ b/packages/schema-core/src/extensions.ts @@ -0,0 +1,29 @@ +export type { + LowerBound as t_LowerBound, + Schema as t_Schema, +} from './types.js' +export { + enum as t_enum, +} from './enum.js' + +export type { never as t_never } from './never.js' +export type { any as t_any } from './any.js' +export type { unknown as t_unknown } from './unknown.js' +export type { void as t_void } from './void.js' +export type { null as t_null } from './null.js' +export type { undefined as t_undefined } from './undefined.js' +export type { symbol as t_symbol } from './symbol.js' +export type { boolean as t_boolean } from './boolean.js' +export type { integer as t_integer } from './integer.js' +export type { bigint as t_bigint } from './bigint.js' +export type { number as t_number } from './number.js' +export type { string as t_string } from './string.js' +export type { eq as t_eq } from './eq.js' +export type { optional as t_optional } from './optional.js' +export type { array as t_array } from './array.js' +export type { record as t_record } from './record.js' +export type { union as t_union } from './union.js' +export type { intersect as t_intersect } from './intersect.js' +export type { tuple as t_tuple } from './tuple.js' +export type { object as t_object } from './object.js' +export type { of as t_of } from './of.js' diff --git a/packages/schema/src/has.ts b/packages/schema-core/src/has.ts similarity index 59% rename from packages/schema/src/has.ts rename to packages/schema-core/src/has.ts index 23e8ce9c..2560e2bc 100644 --- a/packages/schema/src/has.ts +++ b/packages/schema-core/src/has.ts @@ -1,18 +1,18 @@ import { has as has_, URI } from '@traversable/registry' -import * as t from './schema.js' - -export const key = t.union(t.string, t.number, t.symbol) +import type { Schema, SchemaLike } from './types.js' +import type { of } from './of.js' +import type * as t from './unknown.js' export interface has { tag: URI.has (u: unknown): u is this['_type'] _type: has_ - def: [path: KS, predicate: S] + get def(): [path: KS, predicate: S] } -export function has(...args: readonly [...path: KS, leafSchema?: S]): has> -export function has(...args: readonly [...path: KS, leafSchema?: S]): has -export function has(...path: readonly [...KS]): has> +export function has(...args: readonly [...path: KS, leafSchema?: S]): has> +export function has(...args: readonly [...path: KS, leafSchema?: S]): has +export function has(...path: readonly [...KS]): has export function has(...args: readonly [...KS]) { return has_(...args) } diff --git a/packages/schema/src/index.ts b/packages/schema-core/src/index.ts similarity index 100% rename from packages/schema/src/index.ts rename to packages/schema-core/src/index.ts diff --git a/packages/schema-core/src/integer.ts b/packages/schema-core/src/integer.ts new file mode 100644 index 00000000..b3413072 --- /dev/null +++ b/packages/schema-core/src/integer.ts @@ -0,0 +1,100 @@ +import type { Integer, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + Math_min, + Math_max, + Number_isSafeInteger, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Bounds } from './bounded.js' +import { carryover, within } from './bounded.js' + +export { integer } + +/** @internal */ +function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedIntegerSchema(u: unknown) { + return integer(u) && within(bounds)(u) + }, carry, integer) +} + +interface integer extends integer.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } +IntegerSchema.tag = URI.integer +IntegerSchema.def = 0 + +const integer = Object_assign( + IntegerSchema, + userDefinitions, +) as integer + +integer.min = function integerMin(minimum) { + return Object_assign( + boundedInteger({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +integer.max = function integerMax(maximum) { + return Object_assign( + boundedInteger({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +integer.between = function integerBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedInteger({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + integer, + bindUserExtensions(integer, userExtensions), +) + +declare namespace integer { + interface core extends integer.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.integer + get def(): this['_type'] + minimum?: number + maximum?: number + } + interface methods { + min>(minimum: Min): integer.Min + max>(maximum: Max): integer.Max + between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maximum: number }] + ? integer.between<[min: X, max: Self['maximum']]> + : integer.min + type Max + = [Self] extends [{ minimum: number }] + ? integer.between<[min: Self['minimum'], max: X]> + : integer.max + interface min extends integer { minimum: Min } + interface max extends integer { maximum: Max } + interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } +} diff --git a/packages/schema-core/src/intersect.ts b/packages/schema-core/src/intersect.ts new file mode 100644 index 00000000..c9bee889 --- /dev/null +++ b/packages/schema-core/src/intersect.ts @@ -0,0 +1,44 @@ +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' + +import type { Entry, IntersectType, Schema, SchemaLike } from './types.js' +import { intersect as intersect$ } from './predicates.js' + +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: readonly unknown[]) { + return intersect.def(schemas) +} + +export interface intersect extends intersect.core { + //<%= Types %> +} + +export namespace intersect { + export let userDefinitions: Record = { + //<%= Definitions %> + } as intersect + export function def(xs: readonly [...T]): intersect + /* v8 ignore next 1 */ + export function def(xs: readonly unknown[]): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const allOf = xs.every(isPredicate) ? intersect$(xs.map(safeCoerce)) : (_?: unknown): _ is unknown => true + function IntersectSchema(src: unknown) { return allOf(src) } + IntersectSchema.tag = URI.intersect + IntersectSchema.def = xs + Object_assign(IntersectSchema, intersect.userDefinitions) + return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) + } +} + +export declare namespace intersect { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.intersect + get def(): S + _type: IntersectType + } + type type> = never | T +} diff --git a/packages/schema-core/src/key.ts b/packages/schema-core/src/key.ts new file mode 100644 index 00000000..d06b365b --- /dev/null +++ b/packages/schema-core/src/key.ts @@ -0,0 +1,6 @@ +import { number } from './number.js' +import { string } from './string.js' +import { symbol } from './symbol.js' +import { union } from './union.js' + +export const key = union(string, number, symbol) diff --git a/packages/schema/src/types.ts b/packages/schema-core/src/label.ts similarity index 95% rename from packages/schema/src/types.ts rename to packages/schema-core/src/label.ts index 42dd9730..35c9035f 100644 --- a/packages/schema/src/types.ts +++ b/packages/schema-core/src/label.ts @@ -1,74 +1,9 @@ -import type { TypeError } from '@traversable/registry' -import type * as t from './schema.js' - -export type Target = S extends Guard ? T : S extends Predicate ? T : never - -export type $ = [keyof S] extends [never] ? unknown : S - -export interface Predicate { - (value: T): boolean - (value?: T): boolean -} - -export type InvalidItem = never | TypeError<'A required element cannot follow an optional element.'> - -export type Guard = { (u: unknown): u is T } -export interface Typeguard { (u: unknown): u is this['_type']; readonly _type: T } - -export type { TypePredicate_ as TypePredicate } -type TypePredicate_ = never | TypePredicate<[I, O]> - -interface TypePredicate { - (u: T[0]): u is T[1] - (u: T[1]): boolean -} - -export type ValidateTuple< - T extends readonly unknown[], - LowerBound = t.optional, - V = ValidateOptionals<[...T], LowerBound>, -> = [V] extends [['ok']] ? T : V - -export type ValidateOptionals, Acc extends unknown[] = []> - = LowerBound extends S[number] - ? S extends [infer H, ...infer T] - ? LowerBound extends H - ? T[number] extends LowerBound - ? ['ok'] - : [...Acc, H, ...{ [Ix in keyof T]: T[Ix] extends LowerBound ? T[Ix] : InvalidItem }] - : ValidateOptionals - : ['ok'] - : ['ok'] - ; - -export type Label< - Req extends readonly any[], - Opt extends readonly any[] -> = [ - ...Label.Required, - /* @ts-expect-error */ - ...Label.Optional, - ] - -export declare namespace Label { - type Required< - S extends readonly unknown[], - _ = REQ[S['length'] & keyof REQ] - > = { [I in keyof _]: S[I & keyof S] } - - type Optional< - Offset extends number, - Base extends any[], - /* @ts-expect-error */ - Start = OPT[Offset][Base['length']] - > = { [I in keyof Start]: Base[I & keyof Base] } -} /** * ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ */ -type REQ = typeof REQ -declare const REQ: { +export type REQ = typeof REQ +export declare const REQ: { [0x00]: [] [0x01]: [ᵃ: 0x01] [0x02]: [ᵃ: 0x01, ᵇ: 0x02] @@ -101,8 +36,8 @@ declare const REQ: { /** * ᵃᵇᶜᵈᵉᶠᵍʰⁱʲᵏˡᵐⁿᵒᵖʳˢᵗᵘᵛʷˣʸᶻ */ -type OPT = typeof OPT -declare const OPT: { +export type OPT = typeof OPT +export declare const OPT: { [0x00]: { [0x00]: [] [0x01]: [ᵃ?: 0x01] diff --git a/packages/schema-core/src/namespace.ts b/packages/schema-core/src/namespace.ts new file mode 100644 index 00000000..01999697 --- /dev/null +++ b/packages/schema-core/src/namespace.ts @@ -0,0 +1,81 @@ +export * as recurse from './recursive.js' + +export type { + Boundable, + F, + Fixpoint, + Free, + Inline, + Leaf, + Tag, + typeOf as typeof, +} from './core.ts' + +export { never } from './never.js' +export { any } from './any.js' +export { unknown } from './unknown.js' +export { void } from './void.js' +export { null } from './null.js' +export { undefined } from './undefined.js' +export { symbol } from './symbol.js' +export { boolean } from './boolean.js' +export { integer } from './integer.js' +export { bigint } from './bigint.js' +export { number } from './number.js' +export { string } from './string.js' +export { eq } from './eq.js' +export { optional } from './optional.js' +export { array } from './array.js' +export { record } from './record.js' +export { union } from './union.js' +export { intersect } from './intersect.js' +export { tuple } from './tuple.js' +export { object } from './object.js' +export { nonnullable } from './nonnullable.js' +export { of } from './of.js' +export { readonlyArray } from './readonlyArray.js' + +export { + isLeaf, + isNullary, + isNullaryTag, + isBoundable, + isBoundableTag, + isUnary, + isCore, + Functor, + IndexedFunctor, + fold, + foldWithIndex, + unfold, + tags, +} from './core.js' + +export type { + bottom, + invalid, + top, + Entry, + LowerBound, + Optional, + Predicate, + Required, + Schema, + Typeguard, + UnknownSchema, + Unspecified, +} from './types.js' + +export { has } from './has.js' +export { key } from './key.js' + +/* data-types & combinators */ +export * from './combinators.js' +export { enum } from './enum.js' + +/** + * exported as escape hatches, to prevent collisions with built-in keywords + */ +export { null as null_ } from './null.js' +export { undefined as undefined_ } from './undefined.js' +export { void as void_ } from './void.js' diff --git a/packages/schema-core/src/never.ts b/packages/schema-core/src/never.ts new file mode 100644 index 00000000..a0077281 --- /dev/null +++ b/packages/schema-core/src/never.ts @@ -0,0 +1,37 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { never_ as never } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface never_ extends never_.core { + //<%= Types %> +} + +function NeverSchema(src: unknown): src is never { return false } +NeverSchema.tag = URI.never; +NeverSchema.def = void 0 as never + +const never_ = Object_assign( + NeverSchema, + userDefinitions, +) as never_ + +Object_assign(never_, userExtensions) + +export declare namespace never_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.never + _type: never + get def(): this['_type'] + } +} + diff --git a/packages/schema-core/src/nonnullable.ts b/packages/schema-core/src/nonnullable.ts new file mode 100644 index 00000000..4f3ac083 --- /dev/null +++ b/packages/schema-core/src/nonnullable.ts @@ -0,0 +1,38 @@ +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, Object_assign, URI } from '@traversable/registry' + +export { nonnullable } +interface nonnullable extends nonnullable.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function NonNullableSchema(src: unknown) { return src != null } +const nonnullable = Object_assign( + NonNullableSchema, + userDefinitions, +) as nonnullable + +nonnullable.tag = URI.nonnullable; +(nonnullable.def as {}) = {} + +declare namespace nonnullable { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: {} + tag: URI.nonnullable + get def(): this['_type'] + } +} + +Object_assign( + nonnullable, + bindUserExtensions(nonnullable, userExtensions), +) diff --git a/packages/schema-core/src/null.ts b/packages/schema-core/src/null.ts new file mode 100644 index 00000000..befebeb5 --- /dev/null +++ b/packages/schema-core/src/null.ts @@ -0,0 +1,39 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { null_ as null, null_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface null_ extends null_.core { + //<%= Types %> +} + +function NullSchema(src: unknown): src is null { return src === null } +NullSchema.def = null +NullSchema.tag = URI.null + +const null_ = Object_assign( + NullSchema, + userDefinitions, +) as null_ + +Object_assign( + null_, + userExtensions, +) + +declare namespace null_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.null + _type: null + get def(): this['_type'] + } +} diff --git a/packages/schema-core/src/number.ts b/packages/schema-core/src/number.ts new file mode 100644 index 00000000..5173ea84 --- /dev/null +++ b/packages/schema-core/src/number.ts @@ -0,0 +1,133 @@ +import type { Unknown } from '@traversable/registry' +import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' + +import type { Bounds } from './bounded.js' +import { carryover, within } from './bounded.js' + +export { number_ as number } + +interface number_ extends number_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function NumberSchema(src: unknown) { return typeof src === 'number' } +NumberSchema.tag = URI.number +NumberSchema.def = 0 + +const number_ = Object_assign( + NumberSchema, + userDefinitions, +) as number_ + +number_.min = function numberMin(minimum) { + return Object_assign( + boundedNumber({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +number_.max = function numberMax(maximum) { + return Object_assign( + boundedNumber({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +number_.moreThan = function numberMoreThan(exclusiveMinimum) { + return Object_assign( + boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), + { exclusiveMinimum }, + ) +} +number_.lessThan = function numberLessThan(exclusiveMaximum) { + return Object_assign( + boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), + { exclusiveMaximum }, + ) +} +number_.between = function numberBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedNumber({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + number_, + bindUserExtensions(number_, userExtensions), +) + +function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedNumberSchema(u: unknown) { + return typeof u === 'number' && within(bounds)(u) + }, carry, number_) +} + +declare namespace number_ { + interface core extends number_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.number + get def(): this['_type'] + minimum?: number + maximum?: number + exclusiveMinimum?: number + exclusiveMaximum?: number + } + interface methods { + min(minimum: Min): number_.Min + max(maximum: Max): number_.Max + moreThan(moreThan: Min): ExclusiveMin + lessThan(lessThan: Max): ExclusiveMax + between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.between<[min: X, max: Self['maximum']]> + : number_.min + ; + type Max + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.between<[min: Self['minimum'], max: X]> + : number_.max + ; + type ExclusiveMin + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.maxStrictMin<[min: X, Self['maximum']]> + : number_.moreThan + ; + type ExclusiveMax + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.minStrictMax<[Self['minimum'], min: X]> + : number_.lessThan + ; + interface min extends number_ { minimum: Min } + interface max extends number_ { maximum: Max } + interface moreThan extends number_ { exclusiveMinimum: Min } + interface lessThan extends number_ { exclusiveMaximum: Max } + interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } + interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } + interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } + interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } +} diff --git a/packages/schema-core/src/object.ts b/packages/schema-core/src/object.ts new file mode 100644 index 00000000..fa7a98c9 --- /dev/null +++ b/packages/schema-core/src/object.ts @@ -0,0 +1,80 @@ +import type { Force, SchemaOptions as Options, Unknown } from '@traversable/registry' +import { + applyOptions, + Array_isArray, + bindUserExtensions, + has, + isPredicate, + map, + Object_assign, + Object_keys, + safeCoerce, + symbol, + URI, +} from '@traversable/registry' + +import type { Entry, Optional, Required, Schema, SchemaLike } from './types.js' +import { record$, object as anyObject, object$ } from './predicates.js' + +export { object_ as object } + +function object_< + S extends { [x: string]: Schema }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_< + S extends { [x: string]: SchemaLike }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_(schemas: { [x: string]: Schema }, options?: Options) { + return object_.def(schemas, options) +} + +interface object_ extends object_.core { + //<%= Types %> +} + +namespace object_ { + export let userDefinitions: Record = { + //<%= Definitions %> + } as object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ + /* v8 ignore next 1 */ + export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const keys = Object_keys(xs) + const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) + const req = keys.filter((k) => !has(symbol.optional)(xs[k])) + const objectGuard = !record$(isPredicate)(xs) ? anyObject + : object$(map(xs, safeCoerce), applyOptions($)) + function ObjectSchema(src: unknown) { return objectGuard(src) } + ObjectSchema.tag = URI.object + ObjectSchema.def = xs + ObjectSchema.opt = opt + ObjectSchema.req = req + Object_assign(ObjectSchema, userDefinitions) + return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) + } +} + +declare namespace object_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: object_.type + tag: URI.object + get def(): S + opt: Optional + req: Required + } + type type< + S, + Opt extends Optional = Optional, + Req extends Required = Required, + T = Force< + & { [K in Req]-?: S[K]['_type' & keyof S[K]] } + & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } + > + > = never | T +} diff --git a/packages/schema-core/src/of.ts b/packages/schema-core/src/of.ts new file mode 100644 index 00000000..23886d7e --- /dev/null +++ b/packages/schema-core/src/of.ts @@ -0,0 +1,44 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +import type { + Guarded, + Entry, + SchemaLike, + Guard, +} from './types.js' + +export interface of extends of.core { + //<%= Types %> +} + +export function of(typeguard: S): Entry +export function of(typeguard: S): of +export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { + typeguard.def = typeguard + return globalThis.Object.assign(typeguard, of.prototype) +} + +export namespace of { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(guard: T): of + /* v8 ignore next 6 */ + export function def(guard: T) { + function InlineSchema(src: unknown) { return guard(src) } + InlineSchema.tag = URI.inline + InlineSchema.def = guard + return InlineSchema + } +} + +export declare namespace of { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: Guarded + tag: URI.inline + get def(): S + } + type type> = never | T +} diff --git a/packages/schema-core/src/optional.ts b/packages/schema-core/src/optional.ts new file mode 100644 index 00000000..6a8f1e5a --- /dev/null +++ b/packages/schema-core/src/optional.ts @@ -0,0 +1,48 @@ +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, has, isPredicate, Object_assign, safeCoerce, symbol, URI } from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from './types.js' +import { optional$ } from './predicates.js' + +export function optional(schema: S): optional +export function optional(schema: S): optional> +export function optional(schema: S): optional { return optional.def(schema) } + +export interface optional extends optional.core { + //<%= Types %> +} + +export namespace optional { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): optional + export function def(x: T) { + let userExtensions: Record = { + //<%= Extensions %> + } + const optionalGuard = isPredicate(x) ? optional$(safeCoerce(x)) : (_: unknown) => true + function OptionalSchema(src: unknown) { return optionalGuard(src) } + OptionalSchema.tag = URI.optional + OptionalSchema.def = x + OptionalSchema[symbol.optional] = 1 + Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) + return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) + } + export const is + : (u: unknown) => u is optional + /* v8 ignore next 1 */ + = has('tag', (u) => u === URI.optional) +} + +export declare namespace optional { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.optional + _type: undefined | S['_type' & keyof S] + def: S + [symbol.optional]: number + } + export type type = never | T +} + diff --git a/packages/schema/src/postinstall.ts b/packages/schema-core/src/postinstall.ts similarity index 100% rename from packages/schema/src/postinstall.ts rename to packages/schema-core/src/postinstall.ts diff --git a/packages/schema/src/predicates.ts b/packages/schema-core/src/predicates.ts similarity index 92% rename from packages/schema/src/predicates.ts rename to packages/schema-core/src/predicates.ts index f53b16c1..4d038196 100644 --- a/packages/schema/src/predicates.ts +++ b/packages/schema-core/src/predicates.ts @@ -1,7 +1,9 @@ import type { Intersect, SchemaOptions } from '@traversable/registry' import { symbol as Symbol, URI } from '@traversable/registry' -import type * as t from './schema.js' +import type { Predicate } from './types.js' +import type { optional } from './optional.js' +import type * as t from './undefined.js' export { null_ as null, @@ -140,7 +142,7 @@ function isRequiredSchema(u: unknown): u is (_: unknown) => _ is T { return !!u && !isOptionalSchema(u) } export { isOptionalNotUndefinedSchema as __isOptionalNotUndefinedSchema } -function isOptionalNotUndefinedSchema(u: unknown): u is t.optional { +function isOptionalNotUndefinedSchema(u: unknown): u is optional { return !!u && isOptionalSchema(u) && u.def(undefined) === false } @@ -186,13 +188,13 @@ export function record( } } -function union u is unknown)[]>(guard: readonly [...T]): (u: unknown) => u is T[number] -function union u is unknown)[]>(qs: readonly [...T]) { +export function union u is unknown)[]>(guard: readonly [...T]): (u: unknown) => u is T[number] +export function union u is unknown)[]>(qs: readonly [...T]) { return (u: unknown): u is never => qs.some((q) => q(u)) } -function intersect u is unknown)[]>(guard: readonly [...T]): (u: unknown) => u is Intersect -function intersect u is unknown)[]>(qs: readonly [...T]) { +export function intersect u is unknown)[]>(guard: readonly [...T]): (u: unknown) => u is Intersect +export function intersect u is unknown)[]>(qs: readonly [...T]) { return (u: unknown): u is never => qs.every((q) => q(u)) } @@ -253,7 +255,7 @@ function object$ u is unknown)[]>(...q } export function tuple$(options: Opts): - (qs: T) + (qs: T) => (u: unknown) => u is { [I in keyof T]: Target; } { - return (qs: T): (u: unknown) => u is { [I in keyof T]: Target } => { + return (qs: T): (u: unknown) => u is { [I in keyof T]: Target } => { const checkLength = (xs: readonly unknown[]) => options?.minLength === void 0 ? (xs.length === qs.length) diff --git a/packages/schema-core/src/readonlyArray.ts b/packages/schema-core/src/readonlyArray.ts new file mode 100644 index 00000000..d1c7102b --- /dev/null +++ b/packages/schema-core/src/readonlyArray.ts @@ -0,0 +1,16 @@ +import { URI } from '@traversable/registry' + +import type { Schema, SchemaLike } from './types.js' +import { array } from './array.js' +import { Inline } from './core.js' + +export const readonlyArray: { + (schema: S, readonly: 'readonly'): readonlyArray + (schema: S): readonlyArray> +} = array +export interface readonlyArray { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: ReadonlyArray +} diff --git a/packages/schema-core/src/record.ts b/packages/schema-core/src/record.ts new file mode 100644 index 00000000..0da5c331 --- /dev/null +++ b/packages/schema-core/src/record.ts @@ -0,0 +1,44 @@ +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from './types.js' +import { object as anyObject, record$ } from './predicates.js' + +export function record(schema: S): record +export function record(schema: S): record> +export function record(schema: Schema) { + return record.def(schema) +} + +export interface record extends record.core { + //<%= Types %> +} + +export namespace record { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): record + /* v8 ignore next 1 */ + export function def(x: unknown): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const recordGuard = isPredicate(x) ? record$(safeCoerce(x)) : anyObject + function RecordSchema(src: unknown) { return recordGuard(src) } + RecordSchema.tag = URI.record + RecordSchema.def = x + Object_assign(RecordSchema, record.userDefinitions) + return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) + } +} + +export declare namespace record { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.record + get def(): S + _type: Record + } + export type type> = never | T +} diff --git a/packages/schema/src/recursive.ts b/packages/schema-core/src/recursive.ts similarity index 95% rename from packages/schema/src/recursive.ts rename to packages/schema-core/src/recursive.ts index 91c9d421..35ee5bec 100644 --- a/packages/schema/src/recursive.ts +++ b/packages/schema-core/src/recursive.ts @@ -1,7 +1,8 @@ import type * as T from '@traversable/registry' import { escape, fn, parseKey, typeName, URI } from '@traversable/registry' -import * as t from './schema.js' +import type { Schema } from './types.js' +import * as t from './core.js' /** * Note: strictly speaking, `undefined` is not a valid JSON value. It's @@ -129,13 +130,13 @@ export namespace Recursive { } const fold - : (algebra: T.Algebra) => (term: S) => string + : (algebra: T.Algebra) => (term: S) => string = t.fold as never export const toString - : (schema: t.Schema) => string + : (schema: Schema) => string = fold(Recursive.toString) export const toTypeString - : (schema: t.Schema) => string + : (schema: Schema) => string = (schema) => trim(fold(Recursive.toTypeString)(schema)) diff --git a/packages/schema-core/src/string.ts b/packages/schema-core/src/string.ts new file mode 100644 index 00000000..e19bcae2 --- /dev/null +++ b/packages/schema-core/src/string.ts @@ -0,0 +1,97 @@ +import type { Integer, Unknown } from '@traversable/registry' +import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' + +import type { Bounds } from './bounded.js' +import { carryover, within } from './bounded.js' + +export { string_ as string } + +/** @internal */ +function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedStringSchema(u: unknown) { + return string_(u) && within(bounds)(u.length) + }, carry, string_) +} + +interface string_ extends string_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function StringSchema(src: unknown) { return typeof src === 'string' } +StringSchema.tag = URI.string +StringSchema.def = '' + +const string_ = Object_assign( + StringSchema, + userDefinitions, +) as string_ + +string_.min = function stringMinLength(minLength) { + return Object_assign( + boundedString({ gte: minLength }, carryover(this, 'minLength')), + { minLength }, + ) +} +string_.max = function stringMaxLength(maxLength) { + return Object_assign( + boundedString({ lte: maxLength }, carryover(this, 'maxLength')), + { maxLength }, + ) +} +string_.between = function stringBetween( + min, + max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max)) { + return Object_assign( + boundedString({ gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) +} + +Object_assign( + string_, + bindUserExtensions(string_, userExtensions), +) + +declare namespace string_ { + interface core extends string_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: string + tag: URI.string + get def(): this['_type'] + } + interface methods { + minLength?: number + maxLength?: number + min>(minLength: Min): string_.Min + max>(maxLength: Max): string_.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maxLength: number }] + ? string_.between<[min: Min, max: Self['maxLength']]> + : string_.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? string_.between<[min: Self['minLength'], max: Max]> + : string_.max + ; + interface min extends string_ { minLength: Min } + interface max extends string_ { maxLength: Max } + interface between extends string_ { + minLength: Bounds[0] + maxLength: Bounds[1] + } +} diff --git a/packages/schema-core/src/symbol.ts b/packages/schema-core/src/symbol.ts new file mode 100644 index 00000000..6e192b3e --- /dev/null +++ b/packages/schema-core/src/symbol.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { symbol_ as symbol } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface symbol_ extends symbol_.core { + //<%= Types %> +} + +function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } +SymbolSchema.tag = URI.symbol +SymbolSchema.def = Symbol() + +const symbol_ = Object_assign( + SymbolSchema, + userDefinitions, +) as symbol_ + +Object_assign(symbol_, userExtensions) + +declare namespace symbol_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.symbol + _type: symbol + get def(): this['_type'] + } +} diff --git a/packages/schema-core/src/tuple.ts b/packages/schema-core/src/tuple.ts new file mode 100644 index 00000000..672ac207 --- /dev/null +++ b/packages/schema-core/src/tuple.ts @@ -0,0 +1,90 @@ +import type { + SchemaOptions as Options, + TypeError, + Unknown +} from '@traversable/registry' + +import { + Array_isArray, + bindUserExtensions, + getConfig, + has, + isPredicate, + Object_assign, + parseArgs, + safeCoerce, + symbol, + URI, +} from '@traversable/registry' + +import type { + Entry, + FirstOptionalItem, + invalid, + Schema, + SchemaLike, + TupleType, + ValidateTuple +} from './types.js' +import type { optional } from './optional.js' +import { tuple$ } from './predicates.js' + +export { tuple } + +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> + +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> + +function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { + return tuple.def(...parseArgs(getConfig().schema, args)) +} + +interface tuple extends tuple.core { + //<%= Types %> +} + +namespace tuple { + export let userDefinitions: Record = { + //<%= Definitions %> + } as tuple + export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple + /* v8 ignore next 1 */ + export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const opt = opt_ || xs.findIndex(has(symbol.optional)) + const options = { + ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) + } satisfies tuple.InternalOptions + const tupleGuard = !xs.every(isPredicate) + ? Array_isArray + : tuple$(options)(xs.map(safeCoerce)) + function TupleSchema(src: unknown) { return tupleGuard(src) } + TupleSchema.tag = URI.tuple + TupleSchema.def = xs + TupleSchema.opt = opt + Object_assign(TupleSchema, tuple.userDefinitions) + return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) + } +} + +declare namespace tuple { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.tuple + _type: TupleType + opt: FirstOptionalItem + def: S + } + type type> = never | T + type InternalOptions = { minLength?: number } + type validate = ValidateTuple> + + type from + = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T +} diff --git a/packages/schema-core/src/types.ts b/packages/schema-core/src/types.ts new file mode 100644 index 00000000..ddb59da3 --- /dev/null +++ b/packages/schema-core/src/types.ts @@ -0,0 +1,190 @@ +import type { TypeError, URI, symbol } from '@traversable/registry' +import type { unknown } from './unknown.js' +import type { of } from './of.js' +import type { nonnullable } from './nonnullable.js' +import type { OPT, REQ } from './label.js' +import type { optional } from './optional.js' + +export interface top { tag: URI.top, readonly _type: unknown, def: this['_type'] } +export interface bottom { tag: URI.bottom, readonly _type: never, def: this['_type'] } +export interface invalid<_Err> extends TypeError<''> { tag: URI.never } +export type InvalidItem = never | TypeError<'A required element cannot follow an optional element.'> +export type $ = [keyof S] extends [never] ? unknown : S +export type Entry + = S extends { def: unknown } ? S + : S extends Guard ? of + : S extends globalThis.BooleanConstructor ? nonnullable.core + : S extends (() => infer _ extends boolean) + ? BoolLookup[`${_}`] + : S + +export type Source = T extends (_: infer S) => unknown ? S : unknown +export type Guarded = never | S extends (_: any) => _ is infer T ? T : S +export type Target = S extends Guard ? T : S extends Predicate ? T : never +export type SchemaLike = Schema | Predicate +export type Guard = { (u: unknown): u is T } +export interface Typeguard { (u: unknown): u is this['_type']; readonly _type: T } +export type { TypePredicate_ as TypePredicate } +type TypePredicate_ = never | TypePredicate<[I, O]> +interface TypePredicate { + (u: T[0]): u is T[1] + // (u: T[1]): boolean +} + +export interface Unspecified extends LowerBound { } +export interface LowerBound { + (u: unknown): u is any + tag: string + def?: unknown + _type?: T +} + +export interface UnknownSchema { + (u: unknown): u is any + tag: string + def: unknown + _type: unknown +} + +export interface Schema { + tag?: any + def?: Fn['def'] + _type?: Fn['_type'] + (u: unknown): u is this['_type'] +} + +// extends TypePredicate<[Source, Fn['_type']]> { +// tag?: Fn['tag'] +// def?: Fn['def'] +// _type?: Fn['_type'] +// } + +export interface Predicate { + (value: T): boolean + (value?: T): boolean +} + +export type ValidateTuple< + T extends readonly unknown[], + LowerBound = optional, + V = ValidateOptionals<[...T], LowerBound>, +> = [V] extends [['ok']] ? T : V + +export type ValidateOptionals + = LowerBound extends S[number] + ? S extends [infer H, ...infer T] + ? LowerBound extends Partial + ? T[number] extends LowerBound + ? ['ok'] + : [...Acc, H, ...{ [Ix in keyof T]: T[Ix] extends LowerBound ? T[Ix] : InvalidItem }] + : ValidateOptionals + : ['ok'] + : ['ok'] + ; + +// export type ValidateOptionals, Acc extends unknown[] = []> +// = LowerBound extends S[number] +// ? S extends [infer H, ...infer T] +// ? LowerBound extends Partial +// ? T[number] extends LowerBound +// ? ['ok'] +// : [...Acc, H, ...{ [Ix in keyof T]: T[Ix] extends LowerBound ? T[Ix] : InvalidItem }] +// : ValidateOptionals +// : ['ok'] +// : ['ok'] +// ; + +export type Optional = never | + string extends K ? string + : K extends K ? S[K] extends bottom | { [symbol.optional]: any } ? K + : never + : never + +export type Required = never | + string extends K ? string + : K extends K ? S[K] extends bottom | { [symbol.optional]: any } ? never + : K + : never + +export type IntersectType + = Todo extends readonly [infer H, ...infer T] + ? IntersectType + : Out + +// export type TupleType = never +// | { [symbol.optional]: number } extends Partial +// ? T extends readonly [infer Head, ...infer Tail] +// ? [Head] extends [{ [symbol.optional]: number }] ? Label< +// { [ix in keyof Out]: Out[ix]['_type' & keyof Out[ix]] }, +// { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } +// > +// : TupleType +// : never +// : { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } + +export type TupleType> + = [Opt] extends [never] ? { [ix in keyof S]: S[ix]['_type' & keyof S[ix]] } + : S extends readonly [infer Head, ...infer Tail] + ? symbol.optional extends keyof Head ? Label< + { [ix in keyof Out]: Out[ix]['_type' & keyof Out[ix]] }, + { [ix in keyof S]: S[ix]['_type' & keyof S[ix]] } + > + : TupleType + : { [ix in keyof S]: S[ix]['_type' & keyof S[ix]] } + +// [Self['opt' & keyof Self]] extends [never] ? { [ix in keyof S]: S[ix]['_type' & keyof S[ix]] } + +// never + +// | { tag: URI.optional } extends Partial +// ? +// T extends readonly [infer Head, ...infer Tail] +// ? { tag: URI.optional } extends Partial ? Label< +// { [ix in keyof Out]: Out[ix]['_type' & keyof Out[ix]] }, +// { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } +// > +// : TupleType +// : never +// : { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } + +export type FirstOptionalItem + = S extends readonly [infer H, ...infer T] + ? symbol.optional extends keyof H ? Offset['length'] + // ? { [symbol.optional]?: number } extends Partial ? Offset['length'] + : FirstOptionalItem + : never + +// export type FirstOptionalItem +// = S extends readonly [infer H, ...infer T] +// ? { [symbol.optional]: number } extends Partial ? Offset['length'] +// : FirstOptionalItem +// : never + +export type BoolLookup = never | { + true: top + false: bottom + boolean: unknown.core +} + +export type Label< + Req extends readonly any[], + Opt extends readonly any[] +> = [ + ...Label.Required, + /* @ts-expect-error */ + ...Label.Optional, + ] + +export declare namespace Label { + type Required< + S extends readonly unknown[], + _ = REQ[S['length'] & keyof REQ] + > = { [I in keyof _]: S[I & keyof S] } + + type Optional< + Offset extends number, + Base extends any[], + /* @ts-expect-error */ + Start = OPT[Offset][Base['length']] + > = { [I in keyof Start]: Base[I & keyof Base] } +} diff --git a/packages/schema-core/src/undefined.ts b/packages/schema-core/src/undefined.ts new file mode 100644 index 00000000..0115f168 --- /dev/null +++ b/packages/schema-core/src/undefined.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { undefined_ as undefined } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface undefined_ extends undefined_.core { + //<%= Types %> +} + +function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } +UndefinedSchema.tag = URI.undefined +UndefinedSchema.def = void 0 as undefined + +const undefined_ = Object_assign( + UndefinedSchema, + userDefinitions, +) as undefined_ + +Object_assign(undefined_, userExtensions) + +declare namespace undefined_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.undefined + _type: undefined + get def(): this['_type'] + } +} diff --git a/packages/schema-core/src/union.ts b/packages/schema-core/src/union.ts new file mode 100644 index 00000000..e0587203 --- /dev/null +++ b/packages/schema-core/src/union.ts @@ -0,0 +1,44 @@ +import type { Unknown } from '@traversable/registry' +import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from './types.js' +import { is } from './predicates.js' + +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: unknown[]) { + return union.def(schemas) +} + +export interface union extends union.core { + //<%= Types %> +} + +export namespace union { + export let userDefinitions: Record = { + //<%= Definitions %> + } as Partial> + export function def(xs: T): union + /* v8 ignore next 1 */ + export function def(xs: unknown[]) { + let userExtensions: Record = { + //<%= Extensions %> + } + const anyOf = xs.every(isPredicate) ? is.union(xs.map(safeCoerce)) : is.unknown + function UnionSchema(src: unknown): src is unknown { return anyOf(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.userDefinitions) + return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) + } +} + +export declare namespace union { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.union + _type: union.type + get def(): S + } + type type = never | T +} diff --git a/packages/schema-core/src/unknown.ts b/packages/schema-core/src/unknown.ts new file mode 100644 index 00000000..0a190db3 --- /dev/null +++ b/packages/schema-core/src/unknown.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { unknown_ as unknown } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface unknown_ extends unknown_.core { + //<%= Types %> +} + +function UnknownSchema(src: unknown): src is unknown { return true } +UnknownSchema.tag = URI.unknown +UnknownSchema.def = void 0 as unknown + +const unknown_ = Object_assign( + UnknownSchema, + userDefinitions, +) as unknown_ + +Object_assign(unknown_, userExtensions) + +declare namespace unknown_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.unknown + _type: unknown + get def(): this['_type'] + } +} diff --git a/packages/schema/src/utils.ts b/packages/schema-core/src/utils.ts similarity index 91% rename from packages/schema/src/utils.ts rename to packages/schema-core/src/utils.ts index f6b97948..f92b7e0a 100644 --- a/packages/schema/src/utils.ts +++ b/packages/schema-core/src/utils.ts @@ -1,11 +1,11 @@ import { __get as get_ } from '@traversable/registry' -import type * as t from './schema.js' +import type { Schema } from './types.js' export { get } function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], /* @ts-expect-error */ K2 extends keyof S['def'][K1]['def'], @@ -28,7 +28,7 @@ function get< >(schema: S, ...path: [K1, K2, K3, K4, K5, K6, K7, K8, K9]): V function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], /* @ts-expect-error */ K2 extends keyof S['def'][K1]['def'], @@ -49,7 +49,7 @@ function get< >(schema: S, ...path: [K1, K2, K3, K4, K5, K6, K7, K8]): V function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], /* @ts-expect-error */ K2 extends keyof S['def'][K1]['def'], @@ -68,7 +68,7 @@ function get< >(schema: S, ...path: [K1, K2, K3, K4, K5, K6, K7]): V function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], /* @ts-expect-error */ K2 extends keyof S['def'][K1]['def'], @@ -85,7 +85,7 @@ function get< >(schema: S, ...path: [K1, K2, K3, K4, K5, K6]): V function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], /* @ts-expect-error */ K2 extends keyof S['def'][K1]['def'], @@ -100,7 +100,7 @@ function get< >(schema: S, ...path: [K1, K2, K3, K4, K5]): V function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], /* @ts-expect-error */ K2 extends keyof S['def'][K1]['def'], @@ -113,7 +113,7 @@ function get< >(schema: S, ...path: [K1, K2, K3, K4]): V function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], /* @ts-expect-error */ K2 extends keyof S['def'][K1]['def'], @@ -124,7 +124,7 @@ function get< >(schema: S, ...path: [K1, K2, K3]): V function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], /* @ts-expect-error */ K2 extends keyof S['def'][K1]['def'], @@ -133,28 +133,28 @@ function get< >(schema: S, ...path: [K1, K2]): V function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], V = S['def'][K1] >(schema: S, ...key: [K1]): V function get< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], V = S['def'][K1] >(schema: S, ...path: [K1]): V function get< - S extends t.Schema, + S extends Schema, >(schema: S, ...path: []): S -function get(schema: S, ...path: (keyof any)[]) { +function get(schema: S, ...path: (keyof any)[]) { return get_(schema, path.flatMap((segment) => ['def', segment])) } export { get$ } function get$< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], K2 extends keyof S['def'][K1], K3 extends keyof S['def'][K1][K2], @@ -168,7 +168,7 @@ function get$< >(schema: S, ...path: [K1, K2, K3, K4, K5, K6, K7, K8, K9]): V function get$< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], K2 extends keyof S['def'][K1], K3 extends keyof S['def'][K1][K2], @@ -181,7 +181,7 @@ function get$< >(schema: S, ...path: [K1, K2, K3, K4, K5, K6, K7, K8]): V function get$< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], K2 extends keyof S['def'][K1], K3 extends keyof S['def'][K1][K2], @@ -193,7 +193,7 @@ function get$< >(schema: S, ...path: [K1, K2, K3, K4, K5, K6, K7]): V function get$< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], K2 extends keyof S['def'][K1], K3 extends keyof S['def'][K1][K2], @@ -204,7 +204,7 @@ function get$< >(schema: S, ...path: [K1, K2, K3, K4, K5, K6]): V function get$< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], K2 extends keyof S['def'][K1], K3 extends keyof S['def'][K1][K2], @@ -214,7 +214,7 @@ function get$< >(schema: S, ...path: [K1, K2, K3, K4, K5]): V function get$< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], K2 extends keyof S['def'][K1], K3 extends keyof S['def'][K1][K2], @@ -223,7 +223,7 @@ function get$< >(schema: S, ...path: [K1, K2, K3, K4]): V function get$< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], K2 extends keyof S['def'][K1], K3 extends keyof S['def'][K1][K2], @@ -231,15 +231,15 @@ function get$< >(schema: S, ...path: [K1, K2, K3]): V function get$< - S extends t.Schema, + S extends Schema, K1 extends keyof S['def'], K2 extends keyof S['def'][K1], V = S['def'][K1][K2] >(schema: S, ...path: [K1, K2]): V -function get$(schema: S, ...path: [K1]): V -function get$(schema: S, ...path: []): S +function get$(schema: S, ...path: [K1]): V +function get$(schema: S, ...path: []): S /// impl. -function get$(schema: S, ...path: (keyof any)[]) { +function get$(schema: S, ...path: (keyof any)[]) { return get_(schema, path.length === 0 ? [] : ['def', ...path]) } diff --git a/packages/schema/src/version.ts b/packages/schema-core/src/version.ts similarity index 100% rename from packages/schema/src/version.ts rename to packages/schema-core/src/version.ts diff --git a/packages/schema-core/src/void.ts b/packages/schema-core/src/void.ts new file mode 100644 index 00000000..d178213e --- /dev/null +++ b/packages/schema-core/src/void.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { void_ as void, void_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface void_ extends void_.core { + //<%= Types %> +} + +function VoidSchema(src: unknown): src is void { return src === void 0 } +VoidSchema.tag = URI.void +VoidSchema.def = void 0 as void + +const void_ = Object_assign( + VoidSchema, + userDefinitions, +) as void_ + +Object_assign(void_, userExtensions) + +declare namespace void_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.void + _type: void + get def(): this['_type'] + } +} diff --git a/packages/schema/test/bounded.test.ts b/packages/schema-core/test/bounded.test.ts similarity index 99% rename from packages/schema/test/bounded.test.ts rename to packages/schema-core/test/bounded.test.ts index a5637cf6..b4945b57 100644 --- a/packages/schema/test/bounded.test.ts +++ b/packages/schema-core/test/bounded.test.ts @@ -1,8 +1,8 @@ import * as vi from 'vitest' -import { t, __within as within, __withinBig as withinBig } from '@traversable/schema' +import { t, __within as within, __withinBig as withinBig } from '@traversable/schema-core' import { fc, test } from '@fast-check/vitest' -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema/bounded❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core/bounded❳', () => { vi.it('〖⛳️〗‹ ❲within❳', () => { // SUCCESS vi.assert.isTrue(within({ gt: 0 })(1)) diff --git a/packages/schema-core/test/combinators.test.ts b/packages/schema-core/test/combinators.test.ts new file mode 100644 index 00000000..9e3c3b98 --- /dev/null +++ b/packages/schema-core/test/combinators.test.ts @@ -0,0 +1,69 @@ +import * as vi from 'vitest' +import { t } from '@traversable/schema-core' +import { fc, test } from '@fast-check/vitest' +import * as Seed from './seed.js' + +/** + * (go: fc.LetrecTypedTie) => { + * + * never: Arbitrary<"@traversable/schema-core/URI::never">; + * any: Arbitrary<"@traversable/schema-core/URI::any">; + * unknown: Arbitrary<"@traversable/schema-core/URI::unknown">; + * void: Arbitrary<"@traversable/schema-core/URI::void">; + * null: Arbitrary<"@traversable/schema-core/URI::null">; + * undefined: Arbitrary<"@traversable/schema-core/URI::undefined">; + * symbol: Arbitrary<"@traversable/schema-core/URI::symbol">; + * boolean: Arbitrary<"@traversable/schema-core/URI::boolean">; + * bigint: Arbitrary<"@traversable/schema-core/URI::bigint">; + * number: Arbitrary<"@traversable/schema-core/URI::number">; + * string: Arbitrary<"@traversable/schema-core/URI::string">; + * eq: Arbitrary<["@traversable/schema-core/URI::eq", Fixpoint]>; + * optional: Arbitrary<["@traversable/schema-core/URI::optional", "@traversable/schema-core/URI::never" | "@traversable/schema-core/URI::any" | "@traversable/schema-core/URI::unknown" | "@traversable/schema-core/URI::void" | "@traversable/schema-core/URI::null" | "@traversable/schema-core/URI::undefined" | "@traversable/schema-core/URI::symbol" | "@traversable/schema-core/URI::boolean" | "@traversable/schema-core/URI::bigint" | "@traversable/schema-core/URI::number" | "@traversable/schema-core/URI::string" | [tag: "@traversable/schema-core/URI::object", seed: [k: string, Fixpoint][]] | [tag: "@traversable/schema-core/URI::eq", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::array", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::record", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::optional", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::tuple", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::union", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::intersect", seed: readonly Fixpoint[]]]>; + * array: Arbitrary<["@traversable/schema-core/URI::array", "@traversable/schema-core/URI::never" | "@traversable/schema-core/URI::any" | "@traversable/schema-core/URI::unknown" | "@traversable/schema-core/URI::void" | "@traversable/schema-core/URI::null" | "@traversable/schema-core/URI::undefined" | "@traversable/schema-core/URI::symbol" | "@traversable/schema-core/URI::boolean" | "@traversable/schema-core/URI::bigint" | "@traversable/schema-core/URI::number" | "@traversable/schema-core/URI::string" | [tag: "@traversable/schema-core/URI::object", seed: [k: string, Fixpoint][]] | [tag: "@traversable/schema-core/URI::eq", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::array", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::record", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::optional", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::tuple", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::union", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::intersect", seed: readonly Fixpoint[]]]>; + * record: Arbitrary<["@traversable/schema-core/URI::record", "@traversable/schema-core/URI::never" | "@traversable/schema-core/URI::any" | "@traversable/schema-core/URI::unknown" | "@traversable/schema-core/URI::void" | "@traversable/schema-core/URI::null" | "@traversable/schema-core/URI::undefined" | "@traversable/schema-core/URI::symbol" | "@traversable/schema-core/URI::boolean" | "@traversable/schema-core/URI::bigint" | "@traversable/schema-core/URI::number" | "@traversable/schema-core/URI::string" | [tag: "@traversable/schema-core/URI::object", seed: [k: string, Fixpoint][]] | [tag: "@traversable/schema-core/URI::eq", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::array", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::record", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::optional", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::tuple", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::union", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::intersect", seed: readonly Fixpoint[]]]>; + * tuple: Arbitrary<["@traversable/schema-core/URI::tuple", ("@traversable/schema-core/URI::never" | "@traversable/schema-core/URI::any" | "@traversable/schema-core/URI::unknown" | "@traversable/schema-core/URI::void" | "@traversable/schema-core/URI::null" | "@traversable/schema-core/URI::undefined" | "@traversable/schema-core/URI::symbol" | "@traversable/schema-core/URI::boolean" | "@traversable/schema-core/URI::bigint" | "@traversable/schema-core/URI::number" | "@traversable/schema-core/URI::string" | [tag: "@traversable/schema-core/URI::object", seed: [k: string, Fixpoint][]] | [tag: "@traversable/schema-core/URI::eq", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::array", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::record", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::optional", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::tuple", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::union", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::intersect", seed: readonly Fixpoint[]])[]]>; + * object: Arbitrary<["@traversable/schema-core/URI::object", [k: string, v: "@traversable/schema-core/URI::never" | "@traversable/schema-core/URI::any" | "@traversable/schema-core/URI::unknown" | "@traversable/schema-core/URI::void" | "@traversable/schema-core/URI::null" | "@traversable/schema-core/URI::undefined" | "@traversable/schema-core/URI::symbol" | "@traversable/schema-core/URI::boolean" | "@traversable/schema-core/URI::bigint" | "@traversable/schema-core/URI::number" | "@traversable/schema-core/URI::string" | [tag: "@traversable/schema-core/URI::object", seed: [k: string, Fixpoint][]] | [tag: "@traversable/schema-core/URI::eq", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::array", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::record", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::optional", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::tuple", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::union", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::intersect", seed: readonly Fixpoint[]]][]]>; + * union: Arbitrary<["@traversable/schema-core/URI::union", ("@traversable/schema-core/URI::never" | "@traversable/schema-core/URI::any" | "@traversable/schema-core/URI::unknown" | "@traversable/schema-core/URI::void" | "@traversable/schema-core/URI::null" | "@traversable/schema-core/URI::undefined" | "@traversable/schema-core/URI::symbol" | "@traversable/schema-core/URI::boolean" | "@traversable/schema-core/URI::bigint" | "@traversable/schema-core/URI::number" | "@traversable/schema-core/URI::string" | [tag: "@traversable/schema-core/URI::object", seed: [k: string, Fixpoint][]] | [tag: "@traversable/schema-core/URI::eq", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::array", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::record", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::optional", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::tuple", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::union", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::intersect", seed: readonly Fixpoint[]])[]]>; + * intersect: Arbitrary<["@traversable/schema-core/URI::intersect", ("@traversable/schema-core/URI::never" | "@traversable/schema-core/URI::any" | "@traversable/schema-core/URI::unknown" | "@traversable/schema-core/URI::void" | "@traversable/schema-core/URI::null" | "@traversable/schema-core/URI::undefined" | "@traversable/schema-core/URI::symbol" | "@traversable/schema-core/URI::boolean" | "@traversable/schema-core/URI::bigint" | "@traversable/schema-core/URI::number" | "@traversable/schema-core/URI::string" | [tag: "@traversable/schema-core/URI::object", seed: [k: string, Fixpoint][]] | [tag: "@traversable/schema-core/URI::eq", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::array", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::record", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::optional", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::tuple", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::union", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::intersect", seed: readonly Fixpoint[]])[]]>; + * tree: Arbitrary<"@traversable/schema-core/URI::never" | "@traversable/schema-core/URI::any" | "@traversable/schema-core/URI::unknown" | "@traversable/schema-core/URI::void" | "@traversable/schema-core/URI::null" | "@traversable/schema-core/URI::undefined" | "@traversable/schema-core/URI::symbol" | "@traversable/schema-core/URI::boolean" | "@traversable/schema-core/URI::bigint" | "@traversable/schema-core/URI::number" | "@traversable/schema-core/URI::string" | [tag: "@traversable/schema-core/URI::object", seed: [k: string, Fixpoint][]] | [tag: "@traversable/schema-core/URI::eq", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::array", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::record", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::optional", seed: Fixpoint] | [tag: "@traversable/schema-core/URI::tuple", seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::union", * seed: readonly Fixpoint[]] | [tag: "@traversable/schema-core/URI::intersect", seed: readonly Fixpoint[]]>; + * } + */ + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { + const natural = t.filter(t.integer, x => x >= 0) + const varchar = t.filter(t.string)(x => 0x100 >= x.length) + + const arbitrary = fc.letrec(Seed.seed()).tree.chain((seed) => fc.constant([ + Seed.toSchema(seed), + fc.constant(Seed.toJson(seed)), + ] satisfies [any, any])) + + vi.describe('〖⛳️〗‹‹ ❲t.filter❳', () => { + test.prop([fc.nat()])( + '〖⛳️〗‹ ❲t.filter(t.integer, q)❳: returns true when `q` is satisied', + (x) => vi.assert.isTrue(natural(x)) + ) + test.prop([fc.nat().map((x) => -x).filter((x) => x !== 0)])( + '〖⛳️〗‹ ❲t.filter(t.integer, q)❳: returns false when `q` is not satisfied', + (x) => vi.assert.isFalse(natural(x)) + ) + + test.prop([fc.string({ maxLength: 0x100 })])( + '〖⛳️〗‹ ❲t.filter(t.string, q)❳: returns true when `q` is satisfied', + (x) => vi.assert.isTrue(varchar(x)) + ) + test.prop([fc.string({ minLength: 0x101 })], {})( + '〖⛳️〗‹ ❲t.filter(t.string, q)❳: returns false when `q` is not satisfied', + (x) => vi.assert.isFalse(varchar(x)) + ) + + test.prop([arbitrary, fc.func(fc.boolean())])( + /** + * See also: + * https://www.wisdom.weizmann.ac.il/~/oded/VO/mono1.pdf + */ + '〖⛳️〗‹ ❲t.filter(s, q)❳: is monotone cf. s ∩ q', + ([s, x], q) => vi.assert.equal(t.filter(s, q)(x), s(x) && q(x)) + ) + }) +}) diff --git a/packages/schema/test/enum.test.ts b/packages/schema-core/test/enum.test.ts similarity index 57% rename from packages/schema/test/enum.test.ts rename to packages/schema-core/test/enum.test.ts index 28d2f47c..4412c627 100644 --- a/packages/schema/test/enum.test.ts +++ b/packages/schema-core/test/enum.test.ts @@ -1,7 +1,7 @@ import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { vi.it('〖⛳️〗‹ ❲t.enum❳', () => { vi.assert.isFalse(t.enum()(1)) vi.assert.isFalse(t.enum.def([])(1)) diff --git a/packages/schema/test/equals.bench.ts b/packages/schema-core/test/equals.bench.ts similarity index 99% rename from packages/schema/test/equals.bench.ts rename to packages/schema-core/test/equals.bench.ts index e4ca99bb..b15f9528 100644 --- a/packages/schema/test/equals.bench.ts +++ b/packages/schema-core/test/equals.bench.ts @@ -2,7 +2,7 @@ import * as fc from 'fast-check' import * as vi from 'vitest' import lodashIsEqual from 'lodash.isequal' import * as NodeJSUtil from 'node:util' -import { Equal } from '@traversable/schema' +import { Equal } from '@traversable/schema-core' const isEqual = Equal.deep diff --git a/packages/schema/test/equals.test.ts b/packages/schema-core/test/equals.test.ts similarity index 87% rename from packages/schema/test/equals.test.ts rename to packages/schema-core/test/equals.test.ts index 1c426238..72edf1ce 100644 --- a/packages/schema/test/equals.test.ts +++ b/packages/schema-core/test/equals.test.ts @@ -2,9 +2,9 @@ import * as vi from 'vitest' import * as NodeJSUtil from 'node:util' import { fc, test } from '@fast-check/vitest' -import { Equal } from '@traversable/schema' +import { Equal } from '@traversable/schema-core' -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema/equal❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core/equal❳', () => { test.prop([fc.anything(), fc.anything()], { // numRuns: 100_000, })('〖⛳️〗› ❲Equal.deep❳: oracle (NodeJSUtil.isDeepStrictEqual)', (xs, ys) => { diff --git a/packages/schema/test/has.test.ts b/packages/schema-core/test/has.test.ts similarity index 72% rename from packages/schema/test/has.test.ts rename to packages/schema-core/test/has.test.ts index cda17d80..7ae39238 100644 --- a/packages/schema/test/has.test.ts +++ b/packages/schema-core/test/has.test.ts @@ -2,9 +2,9 @@ import * as vi from 'vitest' import { fc, test } from '@fast-check/vitest' import { __fromPath as fromPath } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { let leaf = Symbol.for('leaf') test.prop([fc.array(fc.string())])('〖⛳️〗‹ ❲t.has❳', (path) => { diff --git a/packages/schema/test/inline.test.ts b/packages/schema-core/test/inline.test.ts similarity index 97% rename from packages/schema/test/inline.test.ts rename to packages/schema-core/test/inline.test.ts index ad48aad1..eb165302 100644 --- a/packages/schema/test/inline.test.ts +++ b/packages/schema-core/test/inline.test.ts @@ -1,7 +1,9 @@ import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: support for native type predicates', () => { +let xs = t.optional((u): u is Record<`${boolean}`, boolean> => true) + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳: support for native type predicates', () => { vi.it('〖⛳️〗‹‹‹ ❲t.optional❳: supports native type predicates', () => { vi.assertType>>(t.optional((u): u is Record<`${boolean}`, boolean> => true).def) vi.assertType<{ b: string } | undefined>(t.optional(t.object({ b: t.string }))._type) @@ -288,5 +290,4 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: support for native })._type ) }) - }) diff --git a/packages/schema-core/test/intersect.test.ts b/packages/schema-core/test/intersect.test.ts new file mode 100644 index 00000000..a3d7a683 --- /dev/null +++ b/packages/schema-core/test/intersect.test.ts @@ -0,0 +1,8 @@ +import * as vi from 'vitest' +import { t } from '@traversable/schema-core' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { + vi.it('〖⛳️〗‹ ❲t.intersect❳: if t.intersect.def receives a non-function, it returns `constTrue`', () => { + vi.assert.isTrue(t.intersect.def([1])(1)) + }) +}) diff --git a/packages/schema-core/test/nonnullable.test.ts b/packages/schema-core/test/nonnullable.test.ts new file mode 100644 index 00000000..cf9f3958 --- /dev/null +++ b/packages/schema-core/test/nonnullable.test.ts @@ -0,0 +1,15 @@ +import * as vi from 'vitest' +import { t } from '@traversable/schema-core' +import { fc, test } from '@fast-check/vitest' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { + vi.it('〖⛳️〗‹ ❲t.nonnullable❳: failure case', () => { + vi.assert.isFalse(t.nonnullable(null)) + vi.assert.isFalse(t.nonnullable(void 0)) + }) + test.prop([fc.anything().filter((_) => _ != null)], {})( + '〖⛳️〗‹ ❲t.nonnullable❳: success case', + (_) => vi.assert.isTrue(t.nonnullable(_)) + ) +}) + diff --git a/packages/schema-core/test/optional.test.ts b/packages/schema-core/test/optional.test.ts new file mode 100644 index 00000000..7014bac1 --- /dev/null +++ b/packages/schema-core/test/optional.test.ts @@ -0,0 +1,16 @@ +import * as vi from 'vitest' +import { URI } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { + vi.it('〖⛳️〗‹ ❲t.optional❳: t.optional.is', () => { + vi.assert.isTrue(t.optional.is(t.optional(t.string))) + vi.assert.isTrue(t.optional.is({ tag: URI.optional })) + + vi.assert.isFalse(t.optional.is({ tag: URI.string })) + vi.assert.isFalse(t.optional.is({ tag: 1 })) + vi.assert.isFalse(t.optional.is(1)) + vi.assert.isFalse(t.optional.is(t.undefined)) + vi.assert.isFalse(t.optional.is(t.void)) + }) +}) diff --git a/packages/schema/test/predicates.test.ts b/packages/schema-core/test/predicates.test.ts similarity index 99% rename from packages/schema/test/predicates.test.ts rename to packages/schema-core/test/predicates.test.ts index a791a18c..b47209b2 100644 --- a/packages/schema/test/predicates.test.ts +++ b/packages/schema-core/test/predicates.test.ts @@ -2,10 +2,10 @@ import * as vi from 'vitest' import { fc, test } from '@fast-check/vitest' import { symbol } from '@traversable/registry' -import { Predicate, Predicate as q, t } from '@traversable/schema' +import { Predicate, Predicate as q, t } from '@traversable/schema-core' import * as Seed from './seed.js' -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { const arbitrary = Seed.predicateWithData({ eq: {}, optionalTreatment: 'exactOptional', diff --git a/packages/schema/test/recursive.test.ts b/packages/schema-core/test/recursive.test.ts similarity index 64% rename from packages/schema/test/recursive.test.ts rename to packages/schema-core/test/recursive.test.ts index 2424567c..033a7806 100644 --- a/packages/schema/test/recursive.test.ts +++ b/packages/schema-core/test/recursive.test.ts @@ -1,8 +1,8 @@ import * as vi from 'vitest' -import { recurse, t, __trim as trim } from '@traversable/schema' +import { recurse, t, __trim as trim } from '@traversable/schema-core' -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { vi.it('〖⛳️〗› ❲recurse.trim❳', () => { vi.assert.equal(trim(), 'undefined') }) diff --git a/packages/schema/test/schema.test.ts b/packages/schema-core/test/schema.test.ts similarity index 98% rename from packages/schema/test/schema.test.ts rename to packages/schema-core/test/schema.test.ts index 13dd63a1..790f9f88 100644 --- a/packages/schema/test/schema.test.ts +++ b/packages/schema-core/test/schema.test.ts @@ -13,7 +13,7 @@ import { t, clone, __carryover as carryover, -} from '@traversable/schema' +} from '@traversable/schema-core' import * as Seed from './seed.js' configure({ @@ -143,23 +143,23 @@ const builder * validate property-keys -- only property-values. */ -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: property-based test suite', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core-core❳: property-based test suite', () => { test.prop( [builder().tree, fc.jsonValue()], { // numRuns: 10_000, endOnFailure: true, examples: [ - [["@traversable/schema/URI::eq", { "_": undefined }], {}], + [[URI.eq, { "_": undefined }], {}], // For parity with zod, which does not differentiate between 0 and -0, // we added a configuration option that allows users to pass a custom // "equalsFn", which defaults to IsStrictlyEqual ('===') - [["@traversable/schema/URI::eq", 0], -0], + [[URI.eq, 0], -0], [ [ - "@traversable/schema/URI::union", + URI.union, [ - "@traversable/schema/URI::string", - ["@traversable/schema/URI::eq", { "_O_$M$": "" }] + URI.string, + [URI.eq, { "_O_$M$": "" }] ] ], {} @@ -178,7 +178,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: property-based test ) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { vi.it('〖⛳️〗› ❲t.array❳', () => { const schema_01 = t.array(t.string) vi.assert.isFunction(schema_01) diff --git a/packages/schema/test/seed.ts b/packages/schema-core/test/seed.ts similarity index 97% rename from packages/schema/test/seed.ts rename to packages/schema-core/test/seed.ts index 44d1f752..e1c77688 100644 --- a/packages/schema/test/seed.ts +++ b/packages/schema-core/test/seed.ts @@ -2,10 +2,10 @@ import type * as T from '@traversable/registry' import { fn, parseKey, symbol, URI } from '@traversable/registry' import { Json } from '@traversable/json' -import type { SchemaOptions } from '@traversable/schema' -import { Equal, Predicate, t } from '@traversable/schema' +import type { SchemaOptions } from '@traversable/schema-core' +import { Equal, Predicate, t } from '@traversable/schema-core' -import * as fc from './fast-check.js' +import * as fc from 'fast-check' export { /* model */ @@ -393,7 +393,7 @@ namespace Recursive { } } - export const toSchema: T.Functor.Algebra = (x) => { + export const toSchema: T.Functor.Algebra = (x) => { if (x == null) return x switch (true) { default: return fn.exhaustive(x) @@ -432,7 +432,7 @@ namespace Recursive { case x[0] === URI.eq: return fc.constant(x[1]) case x[0] === URI.array: return fc.array(x[1]) case x[0] === URI.record: return fc.dictionary(fc.string(), x[1]) - case x[0] === URI.optional: return fc.optional(x[1]) + case x[0] === URI.optional: return fc.option(x[1], { nil: undefined }) case x[0] === URI.tuple: return fc.tuple(...x[1]) case x[0] === URI.union: return fc.oneof(...x[1]) case x[0] === URI.object: return fc.record(Object_fromEntries(x[1])) @@ -857,22 +857,33 @@ const Nullaries = { string: fc.constant(URI.string), } + +export type UniqueArrayDefaults = fc.UniqueArrayConstraintsRecommended +let entries = (model: fc.Arbitrary, constraints?: UniqueArrayDefaults) => fc.uniqueArray( + fc.tuple( + fc.stringMatching(new RegExp('^[$_a-zA-Z][$_a-zA-Z0-9]*$', 'u')), + model), + { ...constraints, selector: ([k]) => k } +) + function seed(_: Constraints = defaults) { const $ = parseConstraints(_) - return (go: fc.LetrecTypedTie) => ({ + return (go: fc.LetrecTypedTie): { [K in keyof Builder]: fc.Arbitrary } => ({ ...Nullaries, eq: go('tree').map((_) => [URI.eq, toJson(_)]), array: go('tree').map((_) => [URI.array, _]), record: go('tree').map((_) => [URI.record, _]), - optional: fc.optional(go('tree')).map((_) => [URI.optional, _]), + optional: go('tree').map((_) => [URI.optional, _]), tuple: fc.array(go('tree'), $.tuple).map((_) => [URI.tuple, _.sort(sortSeedOptionalsLast)] satisfies [any, any]), - object: fc.entries(go('tree'), $.object).map((_) => [URI.object, _]), + object: entries(go('tree'), $.object).map((_) => [URI.object, _]), union: fc.array(go('tree'), $.union).map((_) => [URI.union, _]), intersect: fc.array(go('tree'), $.intersect).map((_) => [URI.intersect, _]), tree: fc.oneof($.tree, ...pickAndSortNodes(initialOrder)($).map(go) as ReturnType>[]), } satisfies fc.LetrecValue) } +let zs = fc.letrec(seed()) + const identity = fold(Recursive.identity) // ^? diff --git a/packages/schema/test/utils.test.ts b/packages/schema-core/test/utils.test.ts similarity index 96% rename from packages/schema/test/utils.test.ts rename to packages/schema-core/test/utils.test.ts index 6a015360..78386771 100644 --- a/packages/schema/test/utils.test.ts +++ b/packages/schema-core/test/utils.test.ts @@ -1,5 +1,5 @@ import * as vi from 'vitest' -import { t, get, get$ } from '@traversable/schema' +import { t, get, get$ } from '@traversable/schema-core' import { symbol } from '@traversable/registry' const Schema_01 = t.tuple(t.eq(1)) @@ -26,7 +26,7 @@ const Schema_05 = t.object({ c: t.record(t.boolean) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { vi.it('〖⛳️〗› ❲get❳', () => { vi.assert.equal(get(Schema_01), Schema_01) vi.assert.equal(get(Schema_02), Schema_02) diff --git a/packages/schema/test/version.test.ts b/packages/schema-core/test/version.test.ts similarity index 64% rename from packages/schema/test/version.test.ts rename to packages/schema-core/test/version.test.ts index 00cee146..074a9e1d 100644 --- a/packages/schema/test/version.test.ts +++ b/packages/schema-core/test/version.test.ts @@ -1,8 +1,8 @@ import * as vi from 'vitest' -import { VERSION } from '@traversable/schema' +import { VERSION } from '@traversable/schema-core' import pkg from '../package.json' with { type: 'json' } -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳', () => { vi.it('〖⛳️〗› ❲VERSION❳', () => { const expected = `${pkg.name}@${pkg.version}` vi.assert.equal(VERSION, expected) diff --git a/packages/schema/tsconfig.build.json b/packages/schema-core/tsconfig.build.json similarity index 100% rename from packages/schema/tsconfig.build.json rename to packages/schema-core/tsconfig.build.json diff --git a/packages/schema/tsconfig.json b/packages/schema-core/tsconfig.json similarity index 100% rename from packages/schema/tsconfig.json rename to packages/schema-core/tsconfig.json diff --git a/packages/schema/tsconfig.src.json b/packages/schema-core/tsconfig.src.json similarity index 100% rename from packages/schema/tsconfig.src.json rename to packages/schema-core/tsconfig.src.json diff --git a/packages/schema/tsconfig.test.json b/packages/schema-core/tsconfig.test.json similarity index 100% rename from packages/schema/tsconfig.test.json rename to packages/schema-core/tsconfig.test.json diff --git a/packages/schema/vitest.config.ts b/packages/schema-core/vitest.config.ts similarity index 100% rename from packages/schema/vitest.config.ts rename to packages/schema-core/vitest.config.ts diff --git a/packages/schema-generator/package.json b/packages/schema-generator/package.json index cdfecabc..e1929b39 100644 --- a/packages/schema-generator/package.json +++ b/packages/schema-generator/package.json @@ -47,13 +47,13 @@ }, "peerDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@clack/prompts": "^0.10.1", "@traversable/derive-validators": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-to-json-schema": "workspace:^", "@traversable/schema-to-string": "workspace:^", "picocolors": "^1.1.1" diff --git a/packages/schema-generator/src/__generated__/__manifest__.ts b/packages/schema-generator/src/__generated__/__manifest__.ts index 255d0549..f54f124d 100644 --- a/packages/schema-generator/src/__generated__/__manifest__.ts +++ b/packages/schema-generator/src/__generated__/__manifest__.ts @@ -43,13 +43,13 @@ export default { }, "peerDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@clack/prompts": "^0.10.1", "@traversable/derive-validators": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-to-json-schema": "workspace:^", "@traversable/schema-to-string": "workspace:^", "picocolors": "^1.1.1" diff --git a/packages/schema-generator/src/cli.ts b/packages/schema-generator/src/cli.ts index 9f221f32..2a3ea9a6 100755 --- a/packages/schema-generator/src/cli.ts +++ b/packages/schema-generator/src/cli.ts @@ -7,7 +7,7 @@ import * as p from '@clack/prompts' import color from 'picocolors' import { VERSION } from './version.js' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' let Primitives = t.eq([ { value: 'null', label: 'null' }, @@ -84,7 +84,7 @@ async function prompt() { let TARGET = path.join(process.cwd(), $.target) let PACKAGE_JSON = path.join(TARGET, 'package.json') let NODE_MODULES = path.join(TARGET, 'node_modules') - let TRAVERSABLE_SCHEMA = path.join(NODE_MODULES, '@traversable/schema') + let TRAVERSABLE_SCHEMA = path.join(NODE_MODULES, '@traversable/schema-core') if (!fs.existsSync(TARGET)) { @@ -309,7 +309,7 @@ postinstall() // let TARGET = path.join(process.cwd(), $.target) // let PACKAGE_JSON = path.join(TARGET, 'package.json') // let NODE_MODULES = path.join(TARGET, 'node_modules') -// let TRAVERSABLE_SCHEMA = path.join(NODE_MODULES, '@traversable/schema') +// let TRAVERSABLE_SCHEMA = path.join(NODE_MODULES, '@traversable/schema-core') // if (!fs.existsSync(TARGET)) { diff --git a/packages/schema-generator/src/imports.ts b/packages/schema-generator/src/imports.ts index b03400f5..d4f7b1a8 100644 --- a/packages/schema-generator/src/imports.ts +++ b/packages/schema-generator/src/imports.ts @@ -1,6 +1,6 @@ import type * as T from '@traversable/registry' import { fn, Array_isArray } from "@traversable/registry" -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' let stringComparator: T.Comparator = (l, r) => l.localeCompare(r) @@ -21,7 +21,7 @@ export type ExtensionsBySchemaName = T.Record< T.Record< 'core' | 'equals', T.Record< - '@traversable/registry' | '@traversable/schema', + '@traversable/registry' | '@traversable/schema-core', Imports > > diff --git a/packages/schema-generator/test/e2e.test.ts b/packages/schema-generator/test/__e2e.test.ts__ similarity index 99% rename from packages/schema-generator/test/e2e.test.ts rename to packages/schema-generator/test/__e2e.test.ts__ index a11aec8a..fbc1d511 100644 --- a/packages/schema-generator/test/e2e.test.ts +++ b/packages/schema-generator/test/__e2e.test.ts__ @@ -1,7 +1,7 @@ import * as vi from 'vitest' import * as t from './namespace.js' -import { configure } from '@traversable/schema' +import { configure } from '@traversable/schema-core' import { mut } from '@traversable/registry' vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { diff --git a/packages/schema-generator/test/imports.test.ts b/packages/schema-generator/test/imports.test.ts index 211e1411..469faea0 100644 --- a/packages/schema-generator/test/imports.test.ts +++ b/packages/schema-generator/test/imports.test.ts @@ -4,19 +4,19 @@ import { deduplicateImports, makeImport } from '@traversable/schema-generator' vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { vi.it('〖️⛳️〗› ❲makeImport❳', () => { vi.expect( - makeImport('@traversable/schema', { term: { named: ['t'], namespace: [] }, type: { named: ['Predicate'], namespace: ['T'] } }).join('\n'), + makeImport('@traversable/schema-core', { term: { named: ['t'], namespace: [] }, type: { named: ['Predicate'], namespace: ['T'] } }).join('\n'), ).toMatchInlineSnapshot(` - "import type * as T from '@traversable/schema' - import type { Predicate } from '@traversable/schema' - import { t } from '@traversable/schema'" + "import type * as T from '@traversable/schema-core' + import type { Predicate } from '@traversable/schema-core' + import { t } from '@traversable/schema-core'" `) vi.expect( - makeImport('@traversable/schema', { term: { named: ['t', 'getConfig'], namespace: [] }, type: { named: ['Predicate'], namespace: ['T'] } }).join('\n'), + makeImport('@traversable/schema-core', { term: { named: ['t', 'getConfig'], namespace: [] }, type: { named: ['Predicate'], namespace: ['T'] } }).join('\n'), ).toMatchInlineSnapshot(` - "import type * as T from '@traversable/schema' - import type { Predicate } from '@traversable/schema' - import { t, getConfig } from '@traversable/schema'" + "import type * as T from '@traversable/schema-core' + import type { Predicate } from '@traversable/schema-core' + import { t, getConfig } from '@traversable/schema-core'" `) }) @@ -33,7 +33,7 @@ vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () = named: ['pick'] } }, - "@traversable/schema": { + "@traversable/schema-core": { type: { named: ['t', 'TypeGuard as Guard', 'Bounds'], }, @@ -52,7 +52,7 @@ vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () = namespace: 'T', } }, - "@traversable/schema": { + "@traversable/schema-core": { type: { named: [], }, @@ -109,7 +109,7 @@ vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () = "namespace": Set {}, }, }, - "@traversable/schema": { + "@traversable/schema-core": { "term": { "named": Set { "t", diff --git a/packages/schema-generator/test/namespace.ts b/packages/schema-generator/test/namespace.ts index c833890f..9741df90 100644 --- a/packages/schema-generator/test/namespace.ts +++ b/packages/schema-generator/test/namespace.ts @@ -1,10 +1,10 @@ -export { array } from './__generated__/array.gen.js' -export { integer } from './__generated__/integer.gen.js' -export { intersect } from './__generated__/intersect.gen.js' -export { number } from './__generated__/number.gen.js' -export { object } from './__generated__/object.gen.js' -export { optional } from './__generated__/optional.gen.js' -export { record } from './__generated__/record.gen.js' -export { string } from './__generated__/string.gen.js' -export { tuple } from './__generated__/tuple.gen.js' -export { union } from './__generated__/union.gen.js' +// export { array } from './__generated__/array.gen.js' +// export { integer } from './__generated__/integer.gen.js' +// export { intersect } from './__generated__/intersect.gen.js' +// export { number } from './__generated__/number.gen.js' +// export { object } from './__generated__/object.gen.js' +// export { optional } from './__generated__/optional.gen.js' +// export { record } from './__generated__/record.gen.js' +// export { string } from './__generated__/string.gen.js' +// export { tuple } from './__generated__/tuple.gen.js' +// export { union } from './__generated__/union.gen.js' diff --git a/packages/schema-generator/test/test-data/any/validate.ts b/packages/schema-generator/test/test-data/any/validate.ts index 0bb8a4c4..9c4298c0 100644 --- a/packages/schema-generator/test/test-data/any/validate.ts +++ b/packages/schema-generator/test/test-data/any/validate.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import type { ValidationFn } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/array/core.ts b/packages/schema-generator/test/test-data/array/core.ts index 4a6ae9a5..0767526f 100644 --- a/packages/schema-generator/test/test-data/array/core.ts +++ b/packages/schema-generator/test/test-data/array/core.ts @@ -2,6 +2,7 @@ import type { Integer, Unknown } from '@traversable/registry' import { Array_isArray, bindUserExtensions, + isPredicate, Math_max, Math_min, Number_isSafeInteger, @@ -11,8 +12,8 @@ import { } from '@traversable/registry' -import type { Bounds } from '@traversable/schema' -import { t, __carryover as carryover, __within as within } from '@traversable/schema' +import type { Bounds } from '@traversable/schema-core' +import { t, __carryover as carryover, __within as within } from '@traversable/schema-core' export interface array extends array.core { //<%= Types %> @@ -35,7 +36,7 @@ export namespace array { let userDefinitions: Record = { //<%= Extensions %> } - const arrayPredicate = t.isPredicate(x) ? array$(safeCoerce(x)) : Array_isArray + const arrayPredicate = isPredicate(x) ? array$(safeCoerce(x)) : Array_isArray function ArraySchema(src: unknown) { return arrayPredicate(src) } ArraySchema.tag = URI.array ArraySchema.def = x diff --git a/packages/schema-generator/test/test-data/array/equals.ts b/packages/schema-generator/test/test-data/array/equals.ts index a0ba5093..3f53621d 100644 --- a/packages/schema-generator/test/test-data/array/equals.ts +++ b/packages/schema-generator/test/test-data/array/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { has, Array_isArray, Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' export type equals = never | Equal diff --git a/packages/schema-generator/test/test-data/array/toJsonSchema.ts b/packages/schema-generator/test/test-data/array/toJsonSchema.ts index 5fce8c6b..cbdda659 100644 --- a/packages/schema-generator/test/test-data/array/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/array/toJsonSchema.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import type * as T from '@traversable/registry' import type { SizeBounds } from '@traversable/schema-to-json-schema' import { hasSchema } from '@traversable/schema-to-json-schema' diff --git a/packages/schema-generator/test/test-data/array/toString.ts b/packages/schema-generator/test/test-data/array/toString.ts index 25ecb159..58eee17d 100644 --- a/packages/schema-generator/test/test-data/array/toString.ts +++ b/packages/schema-generator/test/test-data/array/toString.ts @@ -1,4 +1,4 @@ -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export interface toString { /* @ts-expect-error */ diff --git a/packages/schema-generator/test/test-data/array/validate.ts b/packages/schema-generator/test/test-data/array/validate.ts index 0c1438d4..4d8e7958 100644 --- a/packages/schema-generator/test/test-data/array/validate.ts +++ b/packages/schema-generator/test/test-data/array/validate.ts @@ -1,5 +1,5 @@ import { URI } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' import { Errors, NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/bigint/core.ts b/packages/schema-generator/test/test-data/bigint/core.ts index 040c8f59..9e336a18 100644 --- a/packages/schema-generator/test/test-data/bigint/core.ts +++ b/packages/schema-generator/test/test-data/bigint/core.ts @@ -5,8 +5,8 @@ import { bindUserExtensions, } from '@traversable/registry' -import type { Bounds } from '@traversable/schema' -import { __carryover as carryover, __withinBig as within } from '@traversable/schema' +import type { Bounds } from '@traversable/schema-core' +import { __carryover as carryover, __withinBig as within } from '@traversable/schema-core' export let userDefinitions: Record = { //<%= Definitions %> diff --git a/packages/schema-generator/test/test-data/bigint/toJsonSchema.ts b/packages/schema-generator/test/test-data/bigint/toJsonSchema.ts index 8aa548e2..f6c7bc5b 100644 --- a/packages/schema-generator/test/test-data/bigint/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/bigint/toJsonSchema.ts @@ -1,5 +1,3 @@ -import type { t } from '@traversable/schema' - export interface toJsonSchema { (): void } export function toJsonSchema(): toJsonSchema { function bigintToJsonSchema(): void { diff --git a/packages/schema-generator/test/test-data/bigint/validate.ts b/packages/schema-generator/test/test-data/bigint/validate.ts index a25cad0b..7e3284da 100644 --- a/packages/schema-generator/test/test-data/bigint/validate.ts +++ b/packages/schema-generator/test/test-data/bigint/validate.ts @@ -1,5 +1,5 @@ import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/boolean/validate.ts b/packages/schema-generator/test/test-data/boolean/validate.ts index 97736de4..76cee261 100644 --- a/packages/schema-generator/test/test-data/boolean/validate.ts +++ b/packages/schema-generator/test/test-data/boolean/validate.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/eq/core.ts b/packages/schema-generator/test/test-data/eq/core.ts index 535d4eb1..38db9930 100644 --- a/packages/schema-generator/test/test-data/eq/core.ts +++ b/packages/schema-generator/test/test-data/eq/core.ts @@ -1,6 +1,6 @@ import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' -import { applyOptions, bindUserExtensions, Object_assign, URI } from '@traversable/registry' -import { t } from '@traversable/schema' +import { applyOptions, bindUserExtensions, isPredicate, Object_assign, URI } from '@traversable/registry' +import { t } from '@traversable/schema-core' export interface eq extends eq.core { //<%= Types %> @@ -21,7 +21,7 @@ export namespace eq { //<%= Extensions %> } const options = applyOptions($) - const eqGuard = t.isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + const eqGuard = isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) function EqSchema(src: unknown) { return eqGuard(src) } EqSchema.tag = URI.tag EqSchema.def = x diff --git a/packages/schema-generator/test/test-data/eq/equals.ts b/packages/schema-generator/test/test-data/eq/equals.ts index 7ffeaf29..9df41231 100644 --- a/packages/schema-generator/test/test-data/eq/equals.ts +++ b/packages/schema-generator/test/test-data/eq/equals.ts @@ -1,5 +1,5 @@ import type { Equal } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export type equals = never | Equal export function equals(eqSchema: t.eq): equals diff --git a/packages/schema-generator/test/test-data/eq/toJsonSchema.ts b/packages/schema-generator/test/test-data/eq/toJsonSchema.ts index 4c998ae9..8edfd03a 100644 --- a/packages/schema-generator/test/test-data/eq/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/eq/toJsonSchema.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' export interface toJsonSchema { (): { const: T } } export function toJsonSchema(eqSchema: t.eq): toJsonSchema diff --git a/packages/schema-generator/test/test-data/eq/toString.ts b/packages/schema-generator/test/test-data/eq/toString.ts index 36fbaef1..3c224bea 100644 --- a/packages/schema-generator/test/test-data/eq/toString.ts +++ b/packages/schema-generator/test/test-data/eq/toString.ts @@ -1,5 +1,5 @@ import type { Key } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { stringify } from '@traversable/schema-to-string' export interface toString { diff --git a/packages/schema-generator/test/test-data/eq/validate.ts b/packages/schema-generator/test/test-data/eq/validate.ts index ddb3f992..2f02ba7d 100644 --- a/packages/schema-generator/test/test-data/eq/validate.ts +++ b/packages/schema-generator/test/test-data/eq/validate.ts @@ -1,5 +1,5 @@ import { Equal, getConfig, URI } from '@traversable/registry' -import { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import type { Validate } from '@traversable/derive-validators' import { Errors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/integer/core.ts b/packages/schema-generator/test/test-data/integer/core.ts index ade6e720..b4c7cff7 100644 --- a/packages/schema-generator/test/test-data/integer/core.ts +++ b/packages/schema-generator/test/test-data/integer/core.ts @@ -8,8 +8,8 @@ import { bindUserExtensions, } from '@traversable/registry' -import type { Bounds } from '@traversable/schema' -import { __carryover as carryover, __within as within } from '@traversable/schema' +import type { Bounds } from '@traversable/schema-core' +import { __carryover as carryover, __within as within } from '@traversable/schema-core' export let userDefinitions: Record = { //<%= Definitions %> diff --git a/packages/schema-generator/test/test-data/integer/toJsonSchema.ts b/packages/schema-generator/test/test-data/integer/toJsonSchema.ts index cc15fa02..d531e292 100644 --- a/packages/schema-generator/test/test-data/integer/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/integer/toJsonSchema.ts @@ -1,5 +1,5 @@ import type { Force, PickIfDefined } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import type { NumericBounds } from '@traversable/schema-to-json-schema' import { getNumericBounds } from '@traversable/schema-to-json-schema' diff --git a/packages/schema-generator/test/test-data/integer/validate.ts b/packages/schema-generator/test/test-data/integer/validate.ts index c3e993fd..5f044c5d 100644 --- a/packages/schema-generator/test/test-data/integer/validate.ts +++ b/packages/schema-generator/test/test-data/integer/validate.ts @@ -1,5 +1,5 @@ import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/intersect/core.ts b/packages/schema-generator/test/test-data/intersect/core.ts index f1a34487..46395b1a 100644 --- a/packages/schema-generator/test/test-data/intersect/core.ts +++ b/packages/schema-generator/test/test-data/intersect/core.ts @@ -1,7 +1,7 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, Object_assign, safeCoerce, URI } from '@traversable/registry' -import type { IntersectType } from '@traversable/schema' -import { t, Predicate } from '@traversable/schema' +import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' +import type { IntersectType } from '@traversable/schema-core' +import { t, Predicate } from '@traversable/schema-core' export interface intersect extends intersect.core { //<%= Types %> @@ -22,7 +22,7 @@ export namespace intersect { let userExtensions: Record = { //<%= Extensions %> } - const allOf = xs.every(t.isPredicate) ? Predicate.is.intersect(xs.map(safeCoerce)) : Predicate.is.unknown + const allOf = xs.every(isPredicate) ? Predicate.is.intersect(xs.map(safeCoerce)) : Predicate.is.unknown function IntersectSchema(src: unknown) { return allOf(src) } IntersectSchema.tag = URI.intersect IntersectSchema.def = xs diff --git a/packages/schema-generator/test/test-data/intersect/equals.ts b/packages/schema-generator/test/test-data/intersect/equals.ts index 316a4fa0..ce880aa1 100644 --- a/packages/schema-generator/test/test-data/intersect/equals.ts +++ b/packages/schema-generator/test/test-data/intersect/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' export type equals = Equal export function equals(intersectSchema: t.intersect<[...S]>): equals diff --git a/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts b/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts index 4f2d1c95..d3942a94 100644 --- a/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/intersect/toJsonSchema.ts @@ -1,5 +1,5 @@ import type { Returns } from '@traversable/registry' -import { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { getSchema } from '@traversable/schema-to-json-schema' export interface toJsonSchema { diff --git a/packages/schema-generator/test/test-data/intersect/toString.ts b/packages/schema-generator/test/test-data/intersect/toString.ts index d5c62fb3..2e179159 100644 --- a/packages/schema-generator/test/test-data/intersect/toString.ts +++ b/packages/schema-generator/test/test-data/intersect/toString.ts @@ -1,6 +1,6 @@ import type { Join } from '@traversable/registry' import { Array_isArray } from '@traversable/registry' -import { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { callToString } from '@traversable/schema-to-string' export interface toString { diff --git a/packages/schema-generator/test/test-data/intersect/validate.ts b/packages/schema-generator/test/test-data/intersect/validate.ts index 04695896..8a586df2 100644 --- a/packages/schema-generator/test/test-data/intersect/validate.ts +++ b/packages/schema-generator/test/test-data/intersect/validate.ts @@ -1,5 +1,5 @@ import { URI } from '@traversable/registry' -import { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' export type validate = Validate diff --git a/packages/schema-generator/test/test-data/never/validate.ts b/packages/schema-generator/test/test-data/never/validate.ts index f316c281..52ce9ee5 100644 --- a/packages/schema-generator/test/test-data/never/validate.ts +++ b/packages/schema-generator/test/test-data/never/validate.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/null/validate.ts b/packages/schema-generator/test/test-data/null/validate.ts index e0336913..b2abe36b 100644 --- a/packages/schema-generator/test/test-data/null/validate.ts +++ b/packages/schema-generator/test/test-data/null/validate.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import type { ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/number/core.ts b/packages/schema-generator/test/test-data/number/core.ts index 04dc9e01..a9681a62 100644 --- a/packages/schema-generator/test/test-data/number/core.ts +++ b/packages/schema-generator/test/test-data/number/core.ts @@ -1,8 +1,8 @@ import type { Unknown } from '@traversable/registry' import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' -import type { Bounds } from '@traversable/schema' -import { __carryover as carryover, __within as within } from '@traversable/schema' +import type { Bounds } from '@traversable/schema-core' +import { __carryover as carryover, __within as within } from '@traversable/schema-core' export { number_ as number } diff --git a/packages/schema-generator/test/test-data/number/equals.ts b/packages/schema-generator/test/test-data/number/equals.ts index 15704197..a599f588 100644 --- a/packages/schema-generator/test/test-data/number/equals.ts +++ b/packages/schema-generator/test/test-data/number/equals.ts @@ -1,6 +1,7 @@ -import { Equal } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { SameValueNumber } from "@traversable/registry" export type equals = Equal export function equals(left: number, right: number): boolean { - return Equal.SameValueNumber(left, right) + return SameValueNumber(left, right) } diff --git a/packages/schema-generator/test/test-data/number/toJsonSchema.ts b/packages/schema-generator/test/test-data/number/toJsonSchema.ts index f64b1b0f..7146d478 100644 --- a/packages/schema-generator/test/test-data/number/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/number/toJsonSchema.ts @@ -1,5 +1,5 @@ import type { Force, PickIfDefined } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import type { NumericBounds } from '@traversable/schema-to-json-schema' import { getNumericBounds } from '@traversable/schema-to-json-schema' diff --git a/packages/schema-generator/test/test-data/number/validate.ts b/packages/schema-generator/test/test-data/number/validate.ts index cb1d5cb0..416f639b 100644 --- a/packages/schema-generator/test/test-data/number/validate.ts +++ b/packages/schema-generator/test/test-data/number/validate.ts @@ -1,5 +1,5 @@ import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/object/core.ts b/packages/schema-generator/test/test-data/object/core.ts index af07bbfd..0c8f05cb 100644 --- a/packages/schema-generator/test/test-data/object/core.ts +++ b/packages/schema-generator/test/test-data/object/core.ts @@ -3,14 +3,15 @@ import { Array_isArray, applyOptions, bindUserExtensions, + isPredicate, map, Object_assign, Object_keys, safeCoerce, URI, } from '@traversable/registry' -import type { SchemaOptions as Options } from '@traversable/schema' -import { t, Predicate } from '@traversable/schema' +import { SchemaOptions as Options } from '@traversable/schema-core' +import { t, Predicate } from '@traversable/schema-core' interface object_ extends object_.core { //<%= Types %> @@ -41,7 +42,7 @@ namespace object_ { const keys = Object_keys(xs) const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => t.optional.is(xs[k])) const req = keys.filter((k) => !t.optional.is(xs[k])) - const objectGuard = !Predicate.record$(t.isPredicate)(xs) ? Predicate.is.anyObject + const objectGuard = !Predicate.record$(isPredicate)(xs) ? Predicate.is.anyObject : Predicate.is.object(map(xs, safeCoerce), applyOptions($)) function ObjectSchema(src: unknown) { return objectGuard(src) } ObjectSchema.tag = URI.object diff --git a/packages/schema-generator/test/test-data/object/equals.ts b/packages/schema-generator/test/test-data/object/equals.ts index 3af35c15..3cd8134f 100644 --- a/packages/schema-generator/test/test-data/object/equals.ts +++ b/packages/schema-generator/test/test-data/object/equals.ts @@ -1,6 +1,6 @@ import type * as T from '@traversable/registry' import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' export type equals = never | T.Equal export function equals(objectSchema: t.object): equals> diff --git a/packages/schema-generator/test/test-data/object/toJsonSchema.ts b/packages/schema-generator/test/test-data/object/toJsonSchema.ts index 90d08777..bc79c90c 100644 --- a/packages/schema-generator/test/test-data/object/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/object/toJsonSchema.ts @@ -2,7 +2,7 @@ import type { Returns } from '@traversable/registry' import { fn, Object_keys } from '@traversable/registry' import type { RequiredKeys } from '@traversable/schema-to-json-schema' import { isRequired, property } from '@traversable/schema-to-json-schema' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export interface toJsonSchema = RequiredKeys> { (): { diff --git a/packages/schema-generator/test/test-data/object/toString.ts b/packages/schema-generator/test/test-data/object/toString.ts index 0cbbdc3d..ad084ffa 100644 --- a/packages/schema-generator/test/test-data/object/toString.ts +++ b/packages/schema-generator/test/test-data/object/toString.ts @@ -1,6 +1,6 @@ import type { Join, UnionToTuple } from '@traversable/registry' import { symbol } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' /** @internal */ type Symbol_optional = typeof Symbol_optional diff --git a/packages/schema-generator/test/test-data/object/validate.ts b/packages/schema-generator/test/test-data/object/validate.ts index 6674d09e..8d66e417 100644 --- a/packages/schema-generator/test/test-data/object/validate.ts +++ b/packages/schema-generator/test/test-data/object/validate.ts @@ -5,7 +5,7 @@ import { typeName, URI, } from '@traversable/registry' -import { t, getConfig } from '@traversable/schema' +import { t, getConfig } from '@traversable/schema-core' import type { ValidationError, Validator, ValidationFn } from '@traversable/derive-validators' import { NullaryErrors, Errors, UnaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/optional/core.ts b/packages/schema-generator/test/test-data/optional/core.ts index e1263610..b5f6ba04 100644 --- a/packages/schema-generator/test/test-data/optional/core.ts +++ b/packages/schema-generator/test/test-data/optional/core.ts @@ -1,6 +1,6 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, has, Object_assign, safeCoerce, symbol, URI } from '@traversable/registry' -import { t, Predicate } from '@traversable/schema' +import { bindUserExtensions, has, isPredicate, Object_assign, safeCoerce, symbol, URI } from '@traversable/registry' +import { t, Predicate } from '@traversable/schema-core' export interface optional extends optional.core { //<%= Types %> @@ -18,7 +18,7 @@ export namespace optional { let userExtensions: Record = { //<%= Extensions %> } - const optionalGuard = t.isPredicate(x) ? Predicate.is.optional(safeCoerce(x)) : (_: unknown) => true + const optionalGuard = isPredicate(x) ? Predicate.is.optional(safeCoerce(x)) : (_: unknown) => true function OptionalSchema(src: unknown) { return optionalGuard(src) } OptionalSchema.tag = URI.optional OptionalSchema.def = x diff --git a/packages/schema-generator/test/test-data/optional/equals.ts b/packages/schema-generator/test/test-data/optional/equals.ts index 11b1d9ac..25944376 100644 --- a/packages/schema-generator/test/test-data/optional/equals.ts +++ b/packages/schema-generator/test/test-data/optional/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' export type equals = never | Equal export function equals(optionalSchema: t.optional): equals diff --git a/packages/schema-generator/test/test-data/optional/toJsonSchema.ts b/packages/schema-generator/test/test-data/optional/toJsonSchema.ts index 6009f506..82bff553 100644 --- a/packages/schema-generator/test/test-data/optional/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/optional/toJsonSchema.ts @@ -1,7 +1,7 @@ import type { Force } from '@traversable/registry' import type { Returns } from '@traversable/registry' import { symbol } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' type Nullable = Force diff --git a/packages/schema-generator/test/test-data/optional/toString.ts b/packages/schema-generator/test/test-data/optional/toString.ts index fa7b8ee4..f4c96cc8 100644 --- a/packages/schema-generator/test/test-data/optional/toString.ts +++ b/packages/schema-generator/test/test-data/optional/toString.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { callToString } from '@traversable/schema-to-string' export interface toString { diff --git a/packages/schema-generator/test/test-data/optional/validate.ts b/packages/schema-generator/test/test-data/optional/validate.ts index fe15bbc7..feb93d53 100644 --- a/packages/schema-generator/test/test-data/optional/validate.ts +++ b/packages/schema-generator/test/test-data/optional/validate.ts @@ -1,5 +1,5 @@ import { URI } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import type { Validate, Validator, ValidationFn } from '@traversable/derive-validators' export type validate = Validate diff --git a/packages/schema-generator/test/test-data/record/core.ts b/packages/schema-generator/test/test-data/record/core.ts index d283b3ac..fc120878 100644 --- a/packages/schema-generator/test/test-data/record/core.ts +++ b/packages/schema-generator/test/test-data/record/core.ts @@ -1,6 +1,6 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, Object_assign, safeCoerce, URI } from '@traversable/registry' -import { t, Predicate } from '@traversable/schema' +import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' +import { t, Predicate } from '@traversable/schema-core' export interface record extends record.core { //<%= Types %> @@ -21,7 +21,7 @@ export namespace record { let userExtensions: Record = { //<%= Extensions %> } - const recordGuard = t.isPredicate(x) ? Predicate.is.record(safeCoerce(x)) : Predicate.is.anyObject + const recordGuard = isPredicate(x) ? Predicate.is.record(safeCoerce(x)) : Predicate.is.anyObject function RecordSchema(src: unknown) { return recordGuard(src) } RecordSchema.tag = URI.record RecordSchema.def = x diff --git a/packages/schema-generator/test/test-data/record/equals.ts b/packages/schema-generator/test/test-data/record/equals.ts index 378f73c8..07addff1 100644 --- a/packages/schema-generator/test/test-data/record/equals.ts +++ b/packages/schema-generator/test/test-data/record/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Array_isArray, Object_is, Object_keys, Object_hasOwn } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' export type equals = never | Equal export function equals(recordSchema: t.record): equals diff --git a/packages/schema-generator/test/test-data/record/toJsonSchema.ts b/packages/schema-generator/test/test-data/record/toJsonSchema.ts index 7aa5e919..2503d568 100644 --- a/packages/schema-generator/test/test-data/record/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/record/toJsonSchema.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import type * as T from '@traversable/registry' import { getSchema } from '@traversable/schema-to-json-schema' diff --git a/packages/schema-generator/test/test-data/record/toString.ts b/packages/schema-generator/test/test-data/record/toString.ts index df26f100..868f3544 100644 --- a/packages/schema-generator/test/test-data/record/toString.ts +++ b/packages/schema-generator/test/test-data/record/toString.ts @@ -1,5 +1,5 @@ import type { Returns } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { callToString } from '@traversable/schema-to-string' export interface toString { diff --git a/packages/schema-generator/test/test-data/record/validate.ts b/packages/schema-generator/test/test-data/record/validate.ts index fc15bd60..6d958004 100644 --- a/packages/schema-generator/test/test-data/record/validate.ts +++ b/packages/schema-generator/test/test-data/record/validate.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { Array_isArray, Object_keys, URI } from '@traversable/registry' import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/string/core.ts b/packages/schema-generator/test/test-data/string/core.ts index 4eca7733..266e5103 100644 --- a/packages/schema-generator/test/test-data/string/core.ts +++ b/packages/schema-generator/test/test-data/string/core.ts @@ -1,8 +1,8 @@ import type { Integer, Unknown } from '@traversable/registry' import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' -import type { Bounds } from '@traversable/schema' -import { __carryover as carryover, __within as within } from '@traversable/schema' +import type { Bounds } from '@traversable/schema-core' +import { __carryover as carryover, __within as within } from '@traversable/schema-core' interface string_ extends string_.core { //<%= Types %> diff --git a/packages/schema-generator/test/test-data/string/toJsonSchema.ts b/packages/schema-generator/test/test-data/string/toJsonSchema.ts index d39b23e1..2956c069 100644 --- a/packages/schema-generator/test/test-data/string/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/string/toJsonSchema.ts @@ -1,5 +1,5 @@ import type { Force, PickIfDefined } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { has } from '@traversable/registry' import type { SizeBounds } from '@traversable/schema-to-json-schema' diff --git a/packages/schema-generator/test/test-data/string/validate.ts b/packages/schema-generator/test/test-data/string/validate.ts index 92461ad5..8ea63e07 100644 --- a/packages/schema-generator/test/test-data/string/validate.ts +++ b/packages/schema-generator/test/test-data/string/validate.ts @@ -1,5 +1,5 @@ import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/symbol/validate.ts b/packages/schema-generator/test/test-data/symbol/validate.ts index eb3a9819..302e11f2 100644 --- a/packages/schema-generator/test/test-data/symbol/validate.ts +++ b/packages/schema-generator/test/test-data/symbol/validate.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import type { ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/tuple/core.ts b/packages/schema-generator/test/test-data/tuple/core.ts index 9a83de43..1c084ea9 100644 --- a/packages/schema-generator/test/test-data/tuple/core.ts +++ b/packages/schema-generator/test/test-data/tuple/core.ts @@ -7,6 +7,7 @@ import type { import { bindUserExtensions, getConfig, + isPredicate, Object_assign, parseArgs, safeCoerce, @@ -17,11 +18,11 @@ import type { FirstOptionalItem, TupleType, ValidateTuple, -} from '@traversable/schema' +} from '@traversable/schema-core' import { t, Predicate, -} from '@traversable/schema' +} from '@traversable/schema-core' interface tuple extends tuple.core { //<%= Types %> @@ -52,7 +53,7 @@ namespace tuple { const options = { ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(t.optional.is) } satisfies tuple.InternalOptions - const tupleGuard = !xs.every(t.isPredicate) + const tupleGuard = !xs.every(isPredicate) ? Predicate.is.anyArray : Predicate.is.tuple(options)(xs.map(safeCoerce)) function TupleSchema(src: unknown) { return tupleGuard(src) } diff --git a/packages/schema-generator/test/test-data/tuple/equals.ts b/packages/schema-generator/test/test-data/tuple/equals.ts index ed1a055f..4d9de15c 100644 --- a/packages/schema-generator/test/test-data/tuple/equals.ts +++ b/packages/schema-generator/test/test-data/tuple/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export type equals = Equal diff --git a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts index f612905a..ed4cf900 100644 --- a/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/tuple/toJsonSchema.ts @@ -1,5 +1,5 @@ import type { Returns } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' import type { MinItems } from '@traversable/schema-to-json-schema' diff --git a/packages/schema-generator/test/test-data/tuple/toString.ts b/packages/schema-generator/test/test-data/tuple/toString.ts index 39954500..b04c3381 100644 --- a/packages/schema-generator/test/test-data/tuple/toString.ts +++ b/packages/schema-generator/test/test-data/tuple/toString.ts @@ -1,6 +1,6 @@ import type { Join } from '@traversable/registry' import { Array_isArray } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { hasToString } from '@traversable/schema-to-string' export interface toString { diff --git a/packages/schema-generator/test/test-data/tuple/validate.ts b/packages/schema-generator/test/test-data/tuple/validate.ts index b06bef4a..d84e3650 100644 --- a/packages/schema-generator/test/test-data/tuple/validate.ts +++ b/packages/schema-generator/test/test-data/tuple/validate.ts @@ -1,5 +1,5 @@ import { URI, Array_isArray } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' import { Errors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/undefined/validate.ts b/packages/schema-generator/test/test-data/undefined/validate.ts index 318bfbad..d69c9d9e 100644 --- a/packages/schema-generator/test/test-data/undefined/validate.ts +++ b/packages/schema-generator/test/test-data/undefined/validate.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import type { ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/union/core.ts b/packages/schema-generator/test/test-data/union/core.ts index 80dcc70b..abedba6a 100644 --- a/packages/schema-generator/test/test-data/union/core.ts +++ b/packages/schema-generator/test/test-data/union/core.ts @@ -1,6 +1,6 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, Object_assign, safeCoerce, URI } from '@traversable/registry' -import { t, Predicate } from '@traversable/schema' +import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' +import { t, Predicate } from '@traversable/schema-core' export interface union extends union.core { //<%= Types %> @@ -21,7 +21,7 @@ export namespace union { let userExtensions: Record = { //<%= Extensions %> } - const anyOf = xs.every(t.isPredicate) ? Predicate.is.union(xs.map(safeCoerce)) : Predicate.is.unknown + const anyOf = xs.every(isPredicate) ? Predicate.is.union(xs.map(safeCoerce)) : Predicate.is.unknown function UnionSchema(src: unknown): src is unknown { return anyOf(src) } UnionSchema.tag = URI.union UnionSchema.def = xs diff --git a/packages/schema-generator/test/test-data/union/equals.ts b/packages/schema-generator/test/test-data/union/equals.ts index ae0a6e92..c0275e69 100644 --- a/packages/schema-generator/test/test-data/union/equals.ts +++ b/packages/schema-generator/test/test-data/union/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' export type equals = Equal export function equals(unionSchema: t.union<[...S]>): equals diff --git a/packages/schema-generator/test/test-data/union/toJsonSchema.ts b/packages/schema-generator/test/test-data/union/toJsonSchema.ts index f025f027..850f9f66 100644 --- a/packages/schema-generator/test/test-data/union/toJsonSchema.ts +++ b/packages/schema-generator/test/test-data/union/toJsonSchema.ts @@ -1,5 +1,5 @@ import type { Returns } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { getSchema } from '@traversable/schema-to-json-schema' export interface toJsonSchema { diff --git a/packages/schema-generator/test/test-data/union/toString.ts b/packages/schema-generator/test/test-data/union/toString.ts index 91129980..6429d3e1 100644 --- a/packages/schema-generator/test/test-data/union/toString.ts +++ b/packages/schema-generator/test/test-data/union/toString.ts @@ -1,6 +1,6 @@ import type { Join } from '@traversable/registry' import { Array_isArray } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { callToString } from '@traversable/schema-to-string' export interface toString { diff --git a/packages/schema-generator/test/test-data/union/validate.ts b/packages/schema-generator/test/test-data/union/validate.ts index 31737e40..ba69fccf 100644 --- a/packages/schema-generator/test/test-data/union/validate.ts +++ b/packages/schema-generator/test/test-data/union/validate.ts @@ -1,5 +1,5 @@ import { URI } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' export type validate = Validate diff --git a/packages/schema-generator/test/test-data/unknown/validate.ts b/packages/schema-generator/test/test-data/unknown/validate.ts index 7f1a9860..d286c4b5 100644 --- a/packages/schema-generator/test/test-data/unknown/validate.ts +++ b/packages/schema-generator/test/test-data/unknown/validate.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import type { ValidationFn } from '@traversable/derive-validators' diff --git a/packages/schema-generator/test/test-data/void/validate.ts b/packages/schema-generator/test/test-data/void/validate.ts index 6dbc61c2..a67fc4e4 100644 --- a/packages/schema-generator/test/test-data/void/validate.ts +++ b/packages/schema-generator/test/test-data/void/validate.ts @@ -1,4 +1,4 @@ -import type { t } from '@traversable/schema' +import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import type { ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema-generator/tsconfig.build.json b/packages/schema-generator/tsconfig.build.json index 3a9eb4e2..be49007b 100644 --- a/packages/schema-generator/tsconfig.build.json +++ b/packages/schema-generator/tsconfig.build.json @@ -7,5 +7,5 @@ "outDir": "build/esm", "stripInternal": true }, - "references": [{ "path": "../registry" }, { "path": "../schema" }] + "references": [{ "path": "../registry" }, { "path": "../schema-core" }] } diff --git a/packages/schema-generator/tsconfig.src.json b/packages/schema-generator/tsconfig.src.json index 446386e4..702668d2 100644 --- a/packages/schema-generator/tsconfig.src.json +++ b/packages/schema-generator/tsconfig.src.json @@ -6,6 +6,6 @@ "types": ["node"], "outDir": "build/src" }, - "references": [{ "path": "../registry" }, { "path": "../schema" }], + "references": [{ "path": "../registry" }, { "path": "../schema-core" }], "include": ["src"] } diff --git a/packages/schema-generator/tsconfig.test.json b/packages/schema-generator/tsconfig.test.json index a1e26e6a..46c1bf86 100644 --- a/packages/schema-generator/tsconfig.test.json +++ b/packages/schema-generator/tsconfig.test.json @@ -10,7 +10,7 @@ { "path": "tsconfig.src.json" }, { "path": "../derive-validators" }, { "path": "../registry" }, - { "path": "../schema" }, + { "path": "../schema-core" }, { "path": "../schema-to-json-schema" }, { "path": "../schema-to-string" } ], diff --git a/packages/schema-seed/package.json b/packages/schema-seed/package.json index 9c20bc07..6192917b 100644 --- a/packages/schema-seed/package.json +++ b/packages/schema-seed/package.json @@ -46,12 +46,12 @@ "peerDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "fast-check": "^3.23.2" } } diff --git a/packages/schema-seed/src/__generated__/__manifest__.ts b/packages/schema-seed/src/__generated__/__manifest__.ts index 865c3a51..2f70aee8 100644 --- a/packages/schema-seed/src/__generated__/__manifest__.ts +++ b/packages/schema-seed/src/__generated__/__manifest__.ts @@ -42,12 +42,12 @@ export default { "peerDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@traversable/json": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "fast-check": "^3.23.2" } } as const \ No newline at end of file diff --git a/packages/schema-seed/src/arbitrary.ts b/packages/schema-seed/src/arbitrary.ts index b6a4ea25..2dc81efb 100644 --- a/packages/schema-seed/src/arbitrary.ts +++ b/packages/schema-seed/src/arbitrary.ts @@ -1,7 +1,7 @@ import type * as T from '@traversable/registry' import { fn, has, URI } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { isSeed, isNullary } from './seed.js' import * as Seed from './seed.js' diff --git a/packages/schema-seed/src/exports.ts b/packages/schema-seed/src/exports.ts index d73e33c8..8eae9134 100644 --- a/packages/schema-seed/src/exports.ts +++ b/packages/schema-seed/src/exports.ts @@ -5,4 +5,4 @@ export type Seed = [T] extends [never] ? import('./seed.js').Fixpoint : import('./seed.js').Seed -export * as Arbitrary from './arbitrary.js' +// export * as Arbitrary from './arbitrary.js' diff --git a/packages/schema-seed/src/fast-check.ts b/packages/schema-seed/src/fast-check.ts index fd63d257..783cfda8 100644 --- a/packages/schema-seed/src/fast-check.ts +++ b/packages/schema-seed/src/fast-check.ts @@ -1,9 +1,7 @@ export * from 'fast-check' import * as fc from 'fast-check' -import type { Force } from '@traversable/registry' import { symbol as Symbol } from '@traversable/registry' -import type { Guard } from '@traversable/schema' export interface Arbitrary extends fc.Arbitrary { readonly [Symbol.optional]?: true @@ -12,25 +10,6 @@ export interface Arbitrary extends fc.Arbitrary { export type { typeOf as typeof } type typeOf = S extends fc.Arbitrary ? T : never -/** @internal */ -const Object_keys = globalThis.Object.keys -/** @internal */ -const Array_isArray = globalThis.Array.isArray -/** @internal */ -const isString: Guard = (u): u is never => typeof u === 'string' -/** @internal */ -const arrayOf - : (p: Guard) => Guard - = (p) => (u): u is never => Array_isArray(u) && u.every(p) -/** @internal */ -const has - : (k: K, p: Guard) => Guard<{ [P in K]: T }> - = (k, p) => (u: unknown): u is never => - !!u && - typeof u === 'object' && - Object.hasOwn(u, k) && - p(u[k as never]) - const PATTERN = { identifier: /^[$_a-zA-Z][$_a-zA-Z0-9]*$/, } as const diff --git a/packages/schema-seed/src/seed.ts b/packages/schema-seed/src/seed.ts index 471cdeb8..c5134f88 100644 --- a/packages/schema-seed/src/seed.ts +++ b/packages/schema-seed/src/seed.ts @@ -3,13 +3,15 @@ * the generated schemas are more likely to be "deeper" without risk of stack overflow */ +import * as fc from 'fast-check' + import type * as T from '@traversable/registry' import { fn, parseKey, unsafeCompact, URI } from '@traversable/registry' import { Json } from '@traversable/json' -import type { SchemaOptions } from '@traversable/schema' -import { t } from '@traversable/schema' +import type { SchemaOptions } from '@traversable/schema-core' +import { t } from '@traversable/schema-core' -import * as fc from './fast-check.js' +// import * as fc from './fast-check.js' export { type Arbitraries, @@ -84,6 +86,60 @@ const isComposite = (u: unknown) => Array_isArray(u) || (u !== null && typeof u /** @internal */ const isNumeric = t.union(t.number, t.bigint) +export type UniqueArrayDefaults = fc.UniqueArrayConstraintsRecommended + +let identifier = fc.stringMatching(new RegExp('^[$_a-zA-Z][$_a-zA-Z0-9]*$', 'u')) + +let entries = (model: fc.Arbitrary, constraints?: UniqueArrayDefaults) => fc.uniqueArray( + fc.tuple( + identifier, + model), + { ...constraints, selector: ([k]) => k } +) + + +declare namespace InferSchema { + type SchemaMap = { + [URI.never]: t.never + [URI.any]: t.any + [URI.unknown]: t.unknown + [URI.void]: t.void + [URI.null]: t.null + [URI.undefined]: t.undefined + [URI.boolean]: t.boolean + [URI.symbol]: t.symbol + [URI.integer]: t.integer + [URI.bigint]: t.bigint + [URI.number]: t.number + [URI.string]: t.string + [URI.eq]: t.eq + [URI.array]: t.array + [URI.optional]: t.optional + [URI.record]: t.record + [URI.union]: t.union + [URI.intersect]: t.intersect + [URI.tuple]: t.tuple + [URI.object]: t.object + } + type LookupSchema = SchemaMap[(T extends Boundable ? T[0] : T) & keyof SchemaMap] + type CatchUnknown = unknown extends T ? SchemaMap[keyof SchemaMap] : T + type fromFixpoint = CatchUnknown< + T extends { 0: infer Head, 1: infer Tail } + ? [Head, Tail] extends [[URI.integer] | [URI.integer, any], any] ? t.integer + : [Head, Tail] extends [URI.eq, any] ? t.eq + : [Head, Tail] extends [URI.optional, Fixpoint] ? t.optional> + : [Head, Tail] extends [URI.array, Fixpoint] ? t.array> + : [Head, Tail] extends [URI.record, Fixpoint] ? t.record> + : [Head, Tail] extends [URI.union, Fixpoint[]] ? t.union<{ [I in keyof Tail]: LookupSchema }> + : [Head, Tail] extends [URI.intersect, Fixpoint[]] ? t.intersect<{ [I in keyof Tail]: LookupSchema }> + : [Head, Tail] extends [URI.tuple, Fixpoint[]] ? t.tuple<{ [I in keyof Tail]: LookupSchema }> + : [Head, Tail] extends [URI.object, infer Entries extends [k: string, v: any][]] ? t.object<{ [E in Entries[number]as E[0]]: LookupSchema }> + : LookupSchema + : unknown + > +} + + /** * If you provide a partial weight map, missing properties will fall back to `0` */ @@ -597,7 +653,7 @@ const NullarySchemaMap = { [URI.null]: t.null, [URI.undefined]: t.undefined, [URI.boolean]: t.boolean, -} as const satisfies Record +} as const satisfies Record const BoundableSchemaMap = { [URI.integer]: (bounds) => { @@ -648,7 +704,7 @@ const NullaryArbitraryMap = { [URI.null]: fc.constant(null), [URI.undefined]: fc.constant(undefined), [URI.boolean]: fc.boolean(), -} as const satisfies Record +} as const satisfies Record> const integerConstraintsFromBounds = (bounds: InclusiveBounds = {}) => { const { @@ -871,10 +927,10 @@ namespace Recursive { } } - export const toSchema: T.Functor.Algebra = (x) => { + export const toSchema: T.Functor.Algebra = (x) => { if (!isSeed(x)) return x // fn.exhaustive(x) switch (true) { - default: return fn.exhaustive(x) + default: return x // fn.exhaustive(x) case isNullary(x): return NullarySchemaMap[x] case x[0] === URI.array: return BoundableSchemaMap[x[0]](x[2], x[1]) case isBoundable(x): return BoundableSchemaMap[x[0]](x[1] as never) @@ -904,7 +960,7 @@ namespace Recursive { } } - export const toArbitrary: T.Functor.Algebra = (x) => { + export const toArbitrary: T.Functor.Algebra> = (x) => { if (!isSeed(x)) return fn.exhaustive(x) switch (true) { default: return fn.exhaustive(x) @@ -912,8 +968,8 @@ namespace Recursive { case isBoundable(x): return BoundableArbitraryMap[x[0]](x[1] as never) case x[0] === URI.eq: return fc.constant(x[1]) case x[0] === URI.array: return BoundableArbitraryMap[x[0]](x[1], x[2]) - case x[0] === URI.record: return fc.dictionary(fc.identifier(), x[1]) - case x[0] === URI.optional: return fc.optional(x[1]) + case x[0] === URI.record: return fc.dictionary(identifier, x[1]) + case x[0] === URI.optional: return fc.option(x[1], { nil: undefined }) case x[0] === URI.tuple: return fc.tuple(...x[1]) case x[0] === URI.union: return fc.oneof(...x[1]) case x[0] === URI.object: { @@ -1208,9 +1264,9 @@ const Unaries = { eq: (fix: fc.Arbitrary, _: TargetConstraints) => fix.chain(() => fc.jsonValue()).map(eqF), array: (fix: fc.Arbitrary, $: TargetConstraints) => fc.tuple(fix, arrayBounds).map(([def, bounds]) => arrayF(def, bounds)), record: (fix: fc.Arbitrary, _: TargetConstraints) => fix.map(recordF), - optional: (fix: fc.Arbitrary, _: TargetConstraints) => fc.optional(fix).map(optionalF), + optional: (fix: fc.Arbitrary, _: TargetConstraints) => fix.map(optionalF), tuple: (fix: fc.Arbitrary, $: TargetConstraints) => fc.array(fix, $.tuple).map(fn.flow((_) => _.sort(sortSeedOptionalsLast), tupleF)), - object: (fix: fc.Arbitrary, $: TargetConstraints) => fc.entries(fix, $.object).map(objectF), + object: (fix: fc.Arbitrary, $: TargetConstraints) => entries(fix, $.object).map(objectF), union: (fix: fc.Arbitrary, $: TargetConstraints) => fc.array(fix, $.union).map(unionF), intersect: (fix: fc.Arbitrary, $: TargetConstraints) => fc.array(fix, $.intersect).map(intersectF), } @@ -1352,7 +1408,7 @@ const minDepth = { record: (seeds: Seeds[], _: TargetConstraints) => fc.oneof(...seeds).map(recordF), optional: (seeds: Seeds[], $: TargetConstraints) => fc.oneof(...seeds).map(optionalF), object: (seeds: Seeds[], $: TargetConstraints) => - fc.array(fc.tuple(fc.identifier(), fc.oneof(...seeds)), { maxLength: $.object.maxLength, minLength: $.object.minLength }).map(objectF), + fc.array(fc.tuple(identifier, fc.oneof(...seeds)), { maxLength: $.object.maxLength, minLength: $.object.minLength }).map(objectF), tuple: (seeds: Seeds[], $: TargetConstraints) => fc.array(fc.oneof(...seeds), { minLength: $.tuple.minLength, maxLength: $.tuple.maxLength }).map(tupleF), union: (seeds: Seeds[], $: TargetConstraints) => @@ -1381,7 +1437,7 @@ function schemaWithMinDepth( let seed = fc.letrec(seedWithChain($)) let seeds = Object.values(seed) let branches = minDepthBranchOrder.filter(((_) => $.include.includes(_ as never) && !$.exclude.includes(_ as never))) - let arb: fc.Arbitrary = seed.tree + let arb = seed.tree while (n-- >= 0) arb = fc.nat(branches.length - 1).chain( (x): fc.Arbitrary< @@ -1395,23 +1451,24 @@ function schemaWithMinDepth( > => { switch (true) { default: return fn.exhaustive(x as never) - case x === 0: return minDepths[x](seeds, $) - case x === 1: return minDepths[x](seeds, $) - case x === 2: return minDepths[x](seeds, $) - case x === 3: return minDepths[x](seeds, $) - case x === 4: return minDepths[x](seeds, $) - case x === 5: return minDepths[x](seeds, $) - case x === 6: return minDepths[x](seeds, $) + case x === 0: return minDepths[x](seeds as never, $) + case x === 1: return minDepths[x](seeds as never, $) + case x === 2: return minDepths[x](seeds as never, $) + case x === 3: return minDepths[x](seeds as never, $) + case x === 4: return minDepths[x](seeds as never, $) + case x === 5: return minDepths[x](seeds as never, $) + case x === 6: return minDepths[x](seeds as never, $) } }); - return arb.map(toSchema) + return arb.map(toSchema as never) } const identity = fold(Recursive.identity) // ^? -const toSchema = fold(Recursive.toSchema) -// ^? +const toSchema + : (fixpoint: T) => InferSchema.fromFixpoint + = fold(Recursive.toSchema) as never const toArbitrary = fold(Recursive.toArbitrary) // ^? @@ -1443,11 +1500,11 @@ function schema< fc.Arbitrary, { tag: `${T.NS}${Exclude}` }>> function schema(constraints?: Constraints): fc.Arbitrary -function schema(constraints?: Constraints) { - return fc.letrec(seed(constraints as never)).tree.map(toSchema) as never +function schema(constraints?: Constraints): {} { + return fc.letrec(seed(constraints as never)).tree.map(toSchema) } -const extensibleArbitrary = (constraints?: Constraints) => +const extensibleArbitrary = (constraints?: Constraints) => fc.letrec(seed(constraints)).tree.map(fold(Recursive.toExtensibleSchema(constraints?.arbitraries))) /** diff --git a/packages/schema-seed/test/seed.test.ts b/packages/schema-seed/test/seed.test.ts index 4a5522fe..5205236d 100644 --- a/packages/schema-seed/test/seed.test.ts +++ b/packages/schema-seed/test/seed.test.ts @@ -2,13 +2,13 @@ import * as vi from 'vitest' import { fc, test } from '@fast-check/vitest' import { URI } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { Seed } from '@traversable/schema-seed' /** @internal */ const builder = fc.letrec(Seed.seed()) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema-seed❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-seed❳', () => { vi.it('〖⛳️〗› ❲Seed.laxMin❳', () => { vi.assert.equal(Seed.laxMin(), void 0) vi.assert.equal(Seed.laxMin(1), 1) @@ -32,7 +32,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema-seed❳', () => { }) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema-seed❳: property tests', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-seed❳: property tests', () => { vi.it('〖⛳️〗› ❲Seed.isBoundable❳', () => { vi.assert.isTrue(Seed.isBoundable([URI.integer])) vi.assert.isTrue(Seed.isBoundable([URI.bigint])) @@ -45,7 +45,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema-seed❳: property tests }) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema-seed❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-seed❳', () => { vi.it('〖⛳️〗› ❲Seed.stringContraintsFromBounds❳', () => { vi.assert.deepEqual( Seed.stringConstraintsFromBounds({ minimum: 250, maximum: 250 }), @@ -262,7 +262,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema-seed❳', () => { }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema-seed❳: property tests', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-seed❳: property tests', () => { test.prop([builder.number], { // numRuns: 10_000, endOnFailure: true, @@ -390,7 +390,9 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema-seed❳: property tests ) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema/seed❳: Seed.toSchema', () => { +let xs = Seed.toSchema(URI.never) + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema/seed❳: Seed.toSchema', () => { vi.it('〖⛳️〗› ❲t.never❳', () => { vi.assert.deepEqual(Seed.toSchema(URI.never), t.never) }) @@ -444,6 +446,9 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema/seed❳: Seed.toSchema' vi.assert.equal((Seed.toSchema([URI.string, { minimum: 1, maximum: 2 }]) as t.string).minLength, 1) vi.assert.equal((Seed.toSchema([URI.string, { minimum: 1, maximum: 2 }]) as t.string).maxLength, 2) }) + + let zss = Seed.toSchema([URI.optional, URI.any]) + vi.it('〖⛳️〗› ❲t.optional(...)❳', () => { vi.assert.equal(Seed.toSchema([URI.optional, URI.any]).tag, URI.optional) vi.assert.equal((Seed.toSchema([URI.optional, URI.any]).def as t.any).tag, URI.any) @@ -461,10 +466,10 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema/seed❳: Seed.toSchema' vi.assert.deepEqual(Seed.toSchema([URI.eq, [0]]).def, [0]) }) vi.it('〖⛳️〗› ❲t.array(...)❳', () => { - vi.assert.equal((Seed.toSchema([URI.array, URI.any, { minimum: 1 }]) as t.array).minLength, 1) - vi.assert.equal((Seed.toSchema([URI.array, URI.any, { maximum: 2 }]) as t.array).maxLength, 2) - vi.assert.equal((Seed.toSchema([URI.array, URI.any, { minimum: 1, maximum: 2 }]) as t.array).minLength, 1) - vi.assert.equal((Seed.toSchema([URI.array, URI.any, { minimum: 1, maximum: 2 }]) as t.array).maxLength, 2) + vi.assert.equal((Seed.toSchema([URI.array, URI.any, { minimum: 1 }])).minLength, 1) + vi.assert.equal((Seed.toSchema([URI.array, URI.any, { maximum: 2 }])).maxLength, 2) + vi.assert.equal((Seed.toSchema([URI.array, URI.any, { minimum: 1, maximum: 2 }])).minLength, 1) + vi.assert.equal((Seed.toSchema([URI.array, URI.any, { minimum: 1, maximum: 2 }])).maxLength, 2) }) vi.it('〖⛳️〗› ❲t.record(...)❳', () => { vi.assert.equal(Seed.toSchema([URI.record, URI.any]).tag, URI.record) @@ -495,7 +500,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema/seed❳: Seed.toSchema' }) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema/seed❳: Seed.fromSchema', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema/seed❳: Seed.fromSchema', () => { vi.it('〖⛳️〗› ❲t.never❳', () => { vi.assert.deepEqual(Seed.fromSchema(t.never), URI.never) }) @@ -596,7 +601,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema/seed❳: Seed.fromSchem }) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema/seed❳: example-based tests', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema/seed❳: example-based tests', () => { vi.it('〖⛳️〗› ❲Seed.toJson❳', () => { vi.assert.isNull(Seed.toJson([URI.eq, URI.null]), URI.null) vi.assert.isUndefined(Seed.toJson([URI.eq, URI.any])) diff --git a/packages/schema-seed/tsconfig.build.json b/packages/schema-seed/tsconfig.build.json index e36720e1..7af09c34 100644 --- a/packages/schema-seed/tsconfig.build.json +++ b/packages/schema-seed/tsconfig.build.json @@ -10,6 +10,6 @@ "references": [ { "path": "../json" }, { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ] } diff --git a/packages/schema-seed/tsconfig.src.json b/packages/schema-seed/tsconfig.src.json index 076cde65..a021e790 100644 --- a/packages/schema-seed/tsconfig.src.json +++ b/packages/schema-seed/tsconfig.src.json @@ -9,7 +9,7 @@ "references": [ { "path": "../json" }, { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ], "include": ["src"] } diff --git a/packages/schema-seed/tsconfig.test.json b/packages/schema-seed/tsconfig.test.json index 5de93ab0..a0d75975 100644 --- a/packages/schema-seed/tsconfig.test.json +++ b/packages/schema-seed/tsconfig.test.json @@ -10,7 +10,7 @@ { "path": "tsconfig.src.json" }, { "path": "../json" }, { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ], "include": ["test"] } diff --git a/packages/schema-to-json-schema/package.json b/packages/schema-to-json-schema/package.json index d386a264..3d2f61a7 100644 --- a/packages/schema-to-json-schema/package.json +++ b/packages/schema-to-json-schema/package.json @@ -48,11 +48,11 @@ }, "peerDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^" } } diff --git a/packages/schema-to-json-schema/src/__generated__/__manifest__.ts b/packages/schema-to-json-schema/src/__generated__/__manifest__.ts index 17196ac3..361f9694 100644 --- a/packages/schema-to-json-schema/src/__generated__/__manifest__.ts +++ b/packages/schema-to-json-schema/src/__generated__/__manifest__.ts @@ -42,11 +42,11 @@ export default { }, "peerDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^" } } as const \ No newline at end of file diff --git a/packages/schema-to-json-schema/src/install.ts b/packages/schema-to-json-schema/src/install.ts index a3b7771c..498e17d4 100644 --- a/packages/schema-to-json-schema/src/install.ts +++ b/packages/schema-to-json-schema/src/install.ts @@ -1,19 +1,21 @@ -import { t } from '@traversable/schema' +import { Object_assign } from '@traversable/registry' +import { t } from '@traversable/schema-core' + import * as JsonSchema from './jsonSchema.js' -import * as prototypes from './prototypes.js' +import * as toJsonSchema from './prototypes.js' -declare module '@traversable/schema' { +declare module '@traversable/schema-core' { interface t_LowerBound extends JsonSchema.LowerBound { } - interface t_never extends JsonSchema.never { } + interface t_void extends JsonSchema.Empty { } + interface t_undefined extends JsonSchema.Empty { } + interface t_symbol extends JsonSchema.Empty { } + interface t_bigint extends JsonSchema.Empty { } + // interface t_never extends JsonSchema.never { } interface t_unknown extends JsonSchema.unknown { } - interface t_void extends JsonSchema.void { } interface t_any extends JsonSchema.any { } interface t_null extends JsonSchema.null { } - interface t_undefined extends JsonSchema.undefined { } - interface t_symbol extends JsonSchema.symbol { } interface t_boolean extends JsonSchema.boolean { } interface t_integer extends JsonSchema.integer { } - interface t_bigint extends JsonSchema.bigint { } interface t_number extends JsonSchema.number { } interface t_string extends JsonSchema.string { } interface t_eq extends JsonSchema.eq { } @@ -33,10 +35,7 @@ void bind() /// /// INSTALL /// ///////////////// - export function bind() { - /** @internal */ - let Object_assign = globalThis.Object.assign /** no JSON schema representation */ Object_assign(t.never, { toJsonSchema: JsonSchema.empty }) Object_assign(t.void, { toJsonSchema: JsonSchema.empty }) @@ -44,20 +43,20 @@ export function bind() { Object_assign(t.symbol, { toJsonSchema: JsonSchema.empty }) Object_assign(t.bigint, { toJsonSchema: JsonSchema.empty }) /** nullary */ - Object_assign(t.unknown, { toJsonSchema: prototypes.unknown }) - Object_assign(t.any, { toJsonSchema: prototypes.any }) - Object_assign(t.null, { toJsonSchema: prototypes.null }) - Object_assign(t.boolean, { toJsonSchema: prototypes.boolean }) - Object_assign(t.integer, { toJsonSchema: prototypes.integer }) - Object_assign(t.number, { toJsonSchema: prototypes.number }) - Object_assign(t.string, { toJsonSchema: prototypes.string }) + Object_assign(t.unknown, { toJsonSchema: toJsonSchema.unknown }) + Object_assign(t.any, { toJsonSchema: toJsonSchema.any }) + Object_assign(t.null, { toJsonSchema: toJsonSchema.null }) + Object_assign(t.boolean, { toJsonSchema: toJsonSchema.boolean }) + Object_assign(t.integer, { toJsonSchema: toJsonSchema.integer }) + Object_assign(t.number, { toJsonSchema: toJsonSchema.number }) + Object_assign(t.string, { toJsonSchema: toJsonSchema.string }) /** unary */ - Object_assign(t.eq.prototype, { toJsonSchema: prototypes.eq }) - Object_assign(t.optional.prototype, { toJsonSchema: prototypes.optional }) - Object_assign(t.array.prototype, { toJsonSchema: prototypes.array }) - Object_assign(t.record.prototype, { toJsonSchema: prototypes.record }) - Object_assign(t.union.prototype, { toJsonSchema: prototypes.union }) - Object_assign(t.intersect.prototype, { toJsonSchema: prototypes.intersect }) - Object_assign(t.tuple.prototype, { toJsonSchema: prototypes.tuple }) - Object_assign(t.object.prototype, { toJsonSchema: prototypes.object }) + Object_assign(t.eq.userDefinitions, { toJsonSchema: toJsonSchema.eq }) + Object_assign(t.optional.userDefinitions, { toJsonSchema: toJsonSchema.optional }) + Object_assign(t.array.userDefinitions, { toJsonSchema: toJsonSchema.array }) + Object_assign(t.record.userDefinitions, { toJsonSchema: toJsonSchema.record }) + Object_assign(t.union.userDefinitions, { toJsonSchema: toJsonSchema.union }) + Object_assign(t.intersect.userDefinitions, { toJsonSchema: toJsonSchema.intersect }) + Object_assign(t.tuple.userDefinitions, { toJsonSchema: toJsonSchema.tuple }) + Object_assign(t.object.userDefinitions, { toJsonSchema: toJsonSchema.object }) } diff --git a/packages/schema-to-json-schema/src/items.ts b/packages/schema-to-json-schema/src/items.ts index 9e2d1408..9285614e 100644 --- a/packages/schema-to-json-schema/src/items.ts +++ b/packages/schema-to-json-schema/src/items.ts @@ -11,7 +11,8 @@ export type MinItems< T, U = { [I in keyof T]: T[I] extends optional ? I : never }, V = Extract, -> = [V] extends [never] ? T['length' & keyof T] : IndexOfFirstOptional +> = [V] extends [never] ? T['length' & keyof T] + : IndexOfFirstOptional export function minItems>(xs: T): Min export function minItems(xs: unknown[]): number { diff --git a/packages/schema-to-json-schema/src/jsonSchema.ts b/packages/schema-to-json-schema/src/jsonSchema.ts index 8cb7a35b..b79d46b7 100644 --- a/packages/schema-to-json-schema/src/jsonSchema.ts +++ b/packages/schema-to-json-schema/src/jsonSchema.ts @@ -1,18 +1,18 @@ -import type { Force, PickIfDefined, Returns } from '@traversable/registry' -import { fn, has, symbol as Sym } from '@traversable/registry' +import type { Force, Mut, PickIfDefined, Returns, NonUnion } from '@traversable/registry' +import { fn, has, symbol } from '@traversable/registry' import type { MinItems } from './items.js' import { minItems } from './items.js' import type { RequiredKeys } from './properties.js' import { getSchema, isRequired, property, wrapOptional } from './properties.js' import * as Spec from './specification.js' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export type { - Unary, Free, Nullary, NumericBounds, + Unary, } from './specification.js' export { is, @@ -34,6 +34,7 @@ export { minItems } from './items.js' export type { + Inductive, JsonSchema, LowerBound, Schema, @@ -54,6 +55,7 @@ export const getNumericBounds = (u: unknown): Spec.NumericBounds => ({ ...has('exclusiveMaximum', t.number)(u) && { exclusiveMaximum: u.exclusiveMaximum }, }) +type Inductive> = [_] extends [never] ? JsonSchema : Mut type JsonSchema = [T] extends [never] ? Spec.JsonSchema : Spec.Unary type StringBounds = Force<{ type: 'string' } & PickIfDefined> type NumberBounds = Force<{ type: 'number' } & PickIfDefined> @@ -79,7 +81,7 @@ interface Schema { toJsonSchema?(): any } export { Empty as never, Empty as void, - Empty as symbol, + Empty, Empty as undefined, Empty as bigint, } @@ -159,7 +161,7 @@ export function eq(value: V) { return { const: value } } export interface optional { toJsonSchema: { - [Sym.optional]: number + [symbol.optional]: number (): Nullable> } } @@ -167,14 +169,14 @@ export interface optional { export function optional(x: T): optional export function optional(x: unknown) { function toJsonSchema() { return getSchema(x) } - toJsonSchema[Sym.optional] = wrapOptional(x) + toJsonSchema[symbol.optional] = wrapOptional(x) return { toJsonSchema, } } export function optionalProto(child: T) { - (optionalProto as any)[Sym.optional] = wrapOptional(child) + (optionalProto as any)[symbol.optional] = wrapOptional(child) return { ...getSchema(child), nullable: true diff --git a/packages/schema-to-json-schema/src/properties.ts b/packages/schema-to-json-schema/src/properties.ts index bd69b5a0..aec4294f 100644 --- a/packages/schema-to-json-schema/src/properties.ts +++ b/packages/schema-to-json-schema/src/properties.ts @@ -1,31 +1,32 @@ -import { has, symbol as Sym } from '@traversable/registry' +import { has, symbol } from '@traversable/registry' +export { symbol } from '@traversable/registry' export type RequiredKeys< T, _K extends keyof T = keyof T, - _Req = _K extends _K ? T[_K]['toJsonSchema' & keyof T[_K]] extends { [Sym.optional]: number } ? never : _K : never + _Req = _K extends _K ? T[_K]['toJsonSchema' & keyof T[_K]] extends { [symbol.optional]: number } ? never : _K : never > = [_Req] extends [never] ? [] : _Req[] export const hasSchema = has('toJsonSchema', (u) => typeof u === 'function') export const getSchema = (u: T) => hasSchema(u) ? u.toJsonSchema() : u export const isRequired = (v: { [x: string]: unknown }) => (k: string) => { - if (has('toJsonSchema', Sym.optional, (x) => typeof x === 'number')(v[k]) && v[k].toJsonSchema[Sym.optional] !== 0) return false - else if (has(Sym.optional, (x) => typeof x === 'number')(v[k]) && v[k][Sym.optional] !== 0) return false + if (has('toJsonSchema', symbol.optional, (x) => typeof x === 'number')(v[k]) && v[k].toJsonSchema[symbol.optional] !== 0) return false + else if (has(symbol.optional, (x) => typeof x === 'number')(v[k]) && v[k][symbol.optional] !== 0) return false else return true } export function wrapOptional(x: unknown) { - return has(Sym.optional, (u) => typeof u === 'number')(x) - ? x[Sym.optional] + 1 + return has(symbol.optional, (u) => typeof u === 'number')(x) + ? x[symbol.optional] + 1 : 1 } export function unwrapOptional(x: unknown) { - if (has(Sym.optional, (u) => typeof u === 'number')(x)) { - const opt = x[Sym.optional] - if (opt === 1) delete (x as Partial)[Sym.optional] - else x[Sym.optional]-- + if (has(symbol.optional, (u) => typeof u === 'number')(x)) { + const opt = x[symbol.optional] + if (opt === 1) delete (x as Partial)[symbol.optional] + else x[symbol.optional]-- } return getSchema(x) } diff --git a/packages/schema-to-json-schema/src/prototypes.ts b/packages/schema-to-json-schema/src/prototypes.ts index c056671b..c23a2f6e 100644 --- a/packages/schema-to-json-schema/src/prototypes.ts +++ b/packages/schema-to-json-schema/src/prototypes.ts @@ -1,8 +1,9 @@ +import type { Returns } from '@traversable/registry' + import * as Spec from './specification.js' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import * as JsonSchema from './jsonSchema.js' -import { symbol as Sym } from '@traversable/registry' export { toJsonSchemaAny as any, @@ -35,7 +36,13 @@ function toJsonSchemaRecord(this: t.record) { return Json function toJsonSchemaUnion(this: t.union) { return JsonSchema.union(this.def) } function toJsonSchemaIntersect(this: t.intersect) { return JsonSchema.intersect(this.def) } function toJsonSchemaTuple(this: t.tuple) { return JsonSchema.tuple(this.def) } -function toJsonSchemaObject(this: t.object) { return JsonSchema.object(this.def) } + +function toJsonSchemaObject>(this: t.object): { + type: 'object' + required: { [I in keyof KS]: KS[I] & string } + properties: { [K in keyof S]: Returns } +} { return JsonSchema.object(this.def) } + function toJsonSchemaArray(this: t.array) { return JsonSchema.array(this.def, { minLength: this.minLength, maxLength: this.maxLength }) } diff --git a/packages/schema-to-json-schema/src/recursive.ts b/packages/schema-to-json-schema/src/recursive.ts index 3623d323..36afced7 100644 --- a/packages/schema-to-json-schema/src/recursive.ts +++ b/packages/schema-to-json-schema/src/recursive.ts @@ -1,6 +1,6 @@ import type * as T from '@traversable/registry' import { fn, URI, symbol } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { isRequired, property } from './properties.js' import type * as Spec from './specification.js' @@ -8,6 +8,70 @@ import type * as Spec from './specification.js' import * as JsonSchema from './jsonSchema.js' type JsonSchema = import('./jsonSchema.js').JsonSchema +type AnySchema = + | t.null + | t.number + | t.string + | t.boolean + | t.unknown + | t.array + | t.tuple + | t.union + | t.intersect + | t.object<{ [x: string]: AnySchema }> + +declare namespace InferSchema { + type SchemaMap = { + null: Const + boolean: Const + integer: Const + number: Const + string: Const + object: fromObjectLike + array: fromArrayLike + } + + type fromJsonSchema = never | LookupRootSchema + + type LookupRootSchema = never + | T extends { const: infer V } ? t.eq + : T extends { allOf: infer X } ? t.intersect<{ [I in keyof X]: LookupSchema }> + : T extends { anyOf: infer X } ? t.union<{ [I in keyof X]: LookupSchema }> + : Catch> + + type LookupSchema = never + | T extends { const: infer V } ? t.eq + : T extends { allOf: any } ? t.intersect + : T extends { anyOf: any } ? t.union + : Catch> + + type Catch = Spec.JsonSchema extends T ? AnySchema : T + + interface Const extends T.HKT { [-1]: T } + + interface fromObjectLike extends T.HKT { + [-1] + : [this[0]] extends [infer T] + ? T extends typeof Spec.RAW.any ? t.unknown + : T extends { additionalProperties: any } ? t.record> + : T extends { properties: infer P, required?: infer KS extends string[] } ? t.object } + & { [K in keyof P as K extends KS[number] ? never : K]: t.optional> } + >> + : AnySchema + : never + } + + interface fromArrayLike extends T.HKT { + [-1] + : [this[0]] extends [infer T] + ? T extends { additionalItems: false, items: infer S } ? t.tuple<{ -readonly [I in keyof S]: LookupSchema }> + : T extends { items: infer S } ? t.array> + : AnySchema + : never + } +} + /** @internal */ const phantom : () => T @@ -146,7 +210,7 @@ export namespace Recursive { * with {@link toJsonSchema} without any loss of information. */ export const fromJsonSchema - : (term: S) => t.LowerBound + : >(term: S) => InferSchema.fromJsonSchema = JsonSchema.fold(Recursive.fromJsonSchema) /** diff --git a/packages/schema-to-json-schema/src/specification.ts b/packages/schema-to-json-schema/src/specification.ts index a3e8058f..4c8216db 100644 --- a/packages/schema-to-json-schema/src/specification.ts +++ b/packages/schema-to-json-schema/src/specification.ts @@ -1,5 +1,5 @@ import type { HKT } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' export const RAW = { any: { type: 'object', properties: {}, nullable: true } satisfies JsonSchema_any, diff --git a/packages/schema-to-json-schema/test/install.test.ts b/packages/schema-to-json-schema/test/install.test.ts index 5bd68909..e77ee9bd 100644 --- a/packages/schema-to-json-schema/test/install.test.ts +++ b/packages/schema-to-json-schema/test/install.test.ts @@ -1,5 +1,5 @@ import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' vi.describe('〖⛳️〗‹‹‹ ❲@traversable/derive-validators❳', () => { vi.it('〖⛳️〗› ❲pre-install❳', () => vi.assert.isFalse(t.has('toJsonSchema')(t.string))) @@ -10,5 +10,3 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/derive-validators❳', () => .catch((e) => vi.assert.fail(e.message)) }) }) - - diff --git a/packages/schema-to-json-schema/test/jsonSchema.test.ts b/packages/schema-to-json-schema/test/jsonSchema.test.ts index aeb6e2c4..98dedbc1 100644 --- a/packages/schema-to-json-schema/test/jsonSchema.test.ts +++ b/packages/schema-to-json-schema/test/jsonSchema.test.ts @@ -3,7 +3,7 @@ import { test } from '@fast-check/vitest' import { deepStrictEqual } from 'node:assert/strict' import { omitMethods, symbol, URI } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { JsonSchema, toJsonSchema, fromJsonSchema } from '@traversable/schema-to-json-schema' import { Seed } from '@traversable/schema-seed' @@ -35,7 +35,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-to-json-schema❳', () }) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: toJsonSchema', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳: toJsonSchema', () => { test.prop([seed], { // numRuns: 50_000 })('〖⛳️〗› ❲fromJsonSchema(...).toJsonSchema❳: roundtrips', (schema) => { @@ -226,7 +226,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: toJsonSchema', () = // TODO: get `jsonSchema` working for inline schemas // vi.it('〖⛳️〗› ❲t.inline❳', () => vi.assert.deepEqual(t.inline((_) => _ instanceof Error).toJsonSchema(), void 0)) - vi.it('〖⛳️〗› ❲t.never❳', () => vi.assert.deepEqual(t.never.toJsonSchema(), void 0)) + vi.it('〖⛳️〗› ❲t.never❳', () => vi.assert.deepEqual((t.never as any).toJsonSchema(), void 0)) vi.it('〖⛳️〗› ❲t.void❳', () => vi.assert.deepEqual(t.void.toJsonSchema(), void 0)) vi.it('〖⛳️〗› ❲t.symbol❳', () => vi.assert.deepEqual(t.symbol.toJsonSchema(), void 0)) vi.it('〖⛳️〗› ❲t.undefined❳', () => vi.assert.deepEqual(t.undefined.toJsonSchema(), void 0)) @@ -414,7 +414,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: toJsonSchema', () = }) vi.it('〖⛳️〗› ❲t.toJsonSchema❳: works with regular schemas', () => { - vi.assert.deepEqual(toJsonSchema(t.never)(), void 0) + vi.assert.deepEqual(toJsonSchema(t.never as never)(), void 0) vi.assert.deepEqual(toJsonSchema(t.bigint)(), void 0) vi.assert.deepEqual(toJsonSchema(t.symbol)(), void 0) vi.assert.deepEqual(toJsonSchema(t.undefined)(), void 0) @@ -647,7 +647,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: toJsonSchema', () = }) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: fromJsonSchema', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳: fromJsonSchema', () => { vi.it('〖⛳️〗› ❲t.fromJsonSchema❳: t.integer', () => { let schema_01 = t.integer let schema_02 = t.integer.min(0) diff --git a/packages/schema-to-json-schema/tsconfig.build.json b/packages/schema-to-json-schema/tsconfig.build.json index cabdfce7..2972409b 100644 --- a/packages/schema-to-json-schema/tsconfig.build.json +++ b/packages/schema-to-json-schema/tsconfig.build.json @@ -9,6 +9,6 @@ }, "references": [ { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ] } diff --git a/packages/schema-to-json-schema/tsconfig.src.json b/packages/schema-to-json-schema/tsconfig.src.json index fa90f525..9416bd78 100644 --- a/packages/schema-to-json-schema/tsconfig.src.json +++ b/packages/schema-to-json-schema/tsconfig.src.json @@ -8,7 +8,7 @@ }, "references": [ { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ], "include": ["src"] } diff --git a/packages/schema-to-json-schema/tsconfig.test.json b/packages/schema-to-json-schema/tsconfig.test.json index 80aa9583..3ad0d988 100644 --- a/packages/schema-to-json-schema/tsconfig.test.json +++ b/packages/schema-to-json-schema/tsconfig.test.json @@ -9,7 +9,7 @@ "references": [ { "path": "tsconfig.src.json" }, { "path": "../registry" }, - { "path": "../schema" }, + { "path": "../schema-core" }, { "path": "../schema-seed" } ], "include": ["test"] diff --git a/packages/schema-to-string/package.json b/packages/schema-to-string/package.json index 2a09efa7..c13f1f9b 100644 --- a/packages/schema-to-string/package.json +++ b/packages/schema-to-string/package.json @@ -48,11 +48,11 @@ }, "peerDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^", "zod": "^3.24.2" } diff --git a/packages/schema-to-string/src/__generated__/__manifest__.ts b/packages/schema-to-string/src/__generated__/__manifest__.ts index d25edeb8..9d3ec919 100644 --- a/packages/schema-to-string/src/__generated__/__manifest__.ts +++ b/packages/schema-to-string/src/__generated__/__manifest__.ts @@ -42,11 +42,11 @@ export default { }, "peerDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^" + "@traversable/schema-core": "workspace:^" }, "devDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema": "workspace:^", + "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^", "zod": "^3.24.2" } diff --git a/packages/schema-to-string/src/install.ts b/packages/schema-to-string/src/install.ts index bb260202..0e304f5b 100644 --- a/packages/schema-to-string/src/install.ts +++ b/packages/schema-to-string/src/install.ts @@ -1,8 +1,8 @@ -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import * as toString from './toString.js' -declare module '@traversable/schema' { - interface t_never extends toString.never { } +declare module '@traversable/schema-core' { + // interface t_never extends toString.never { } interface t_unknown extends toString.unknown { } interface t_void extends toString.void { } interface t_any extends toString.any { } @@ -43,12 +43,12 @@ function bind() { Object.assign(t.bigint, { toString: toString.bigint }) Object.assign(t.number, { toString: toString.number }) Object.assign(t.string, { toString: toString.string }) - Object.assign(t.eq.prototype, { toString: toString.eq }) - Object.assign(t.optional.prototype, { toString: toString.optional }) - Object.assign(t.union.prototype, { toString: toString.union }) - Object.assign(t.intersect.prototype, { toString: toString.intersect }) - Object.assign(t.tuple.prototype, { toString: toString.tuple }) - Object.assign(t.object.prototype, { toString: toString.object }) - Object.assign(t.array.prototype, { toString: toString.array }) - Object.assign(t.record.prototype, { toString: toString.record }) + Object.assign(t.eq.userDefinitions, { toString: toString.eq }) + Object.assign(t.optional.userDefinitions, { toString: toString.optional }) + Object.assign(t.union.userDefinitions, { toString: toString.union }) + Object.assign(t.intersect.userDefinitions, { toString: toString.intersect }) + Object.assign(t.tuple.userDefinitions, { toString: toString.tuple }) + Object.assign(t.object.userDefinitions, { toString: toString.object }) + Object.assign(t.array.userDefinitions, { toString: toString.array }) + Object.assign(t.record.userDefinitions, { toString: toString.record }) } diff --git a/packages/schema-to-string/src/recursive.ts b/packages/schema-to-string/src/recursive.ts index 147faaee..6fe476a6 100644 --- a/packages/schema-to-string/src/recursive.ts +++ b/packages/schema-to-string/src/recursive.ts @@ -1,10 +1,11 @@ import type * as T from '@traversable/registry' import { fn, NS, parseKey, URI } from '@traversable/registry' import { Json } from '@traversable/json' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' /** @internal */ const Object_entries = globalThis.Object.entries + /** @internal */ const OPT = '<<>>' as const /** @internal */ diff --git a/packages/schema-to-string/src/shared.ts b/packages/schema-to-string/src/shared.ts index a32c8895..786e97a7 100644 --- a/packages/schema-to-string/src/shared.ts +++ b/packages/schema-to-string/src/shared.ts @@ -1,4 +1,3 @@ - export let hasToString = (x: unknown): x is { toString(): string } => !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' @@ -13,4 +12,4 @@ export function callToString(x: unknown): string { return hasToString(x) ? x.toS export let stringify : (u: unknown) => string - = (u) => typeof u === 'string' ? `'${u}'` : isShowable(u) ? globalThis.String(u) : 'string' + = (u) => typeof u === 'string' ? `'${u}'` : isShowable(u) ? String(u) : 'string' diff --git a/packages/schema-to-string/src/toString.ts b/packages/schema-to-string/src/toString.ts index 326969d0..9d8741ae 100644 --- a/packages/schema-to-string/src/toString.ts +++ b/packages/schema-to-string/src/toString.ts @@ -1,6 +1,6 @@ import type { Returns, Join, Showable, UnionToTuple } from '@traversable/registry' import { symbol } from '@traversable/registry' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { isShowable, hasToString, @@ -30,17 +30,13 @@ export { objectToString as object, } -/** @internal */ -type Symbol_optional = typeof Symbol_optional -const Symbol_optional: typeof symbol.optional = symbol.optional - /** @internal */ const isArray = globalThis.Array.isArray /** @internal */ const isOptional = (u: unknown): u is { toString(): T } => !!u && typeof u === 'function' && - Symbol_optional in u && - typeof u[Symbol_optional] === 'number' + symbol.optional in u && + typeof u[symbol.optional] === 'number' export function toString(x: unknown): string { return hasToString(x) ? x.toString() : 'unknown' } @@ -60,7 +56,7 @@ export declare namespace toString { export type tuple = never | `[${Join<{ [I in keyof T]: `${ /* @ts-expect-error */ - T[I] extends { [Symbol_optional]: any } ? `_?: ${Returns}` : Returns + symbol.optional extends keyof T[I] ? `_?: ${Returns}` : Returns }` }, ', '>}]` /* @ts-expect-error */ @@ -71,7 +67,7 @@ export declare namespace toString { export type object_> = never | [keyof T] extends [never] ? '{}' /* @ts-expect-error */ - : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${Returns}` }, ', '>} }` + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [symbol.optional]: any } ? `'?` : `'`}: ${Returns}` }, ', '>} }` } toString.never = 'never' as const @@ -135,7 +131,7 @@ function stringToString() { return toString.string } interface eqToString { toString(): Returns> } interface arrayToString { toString(): Returns> } -interface optionalToString { toString(): Returns>, [Symbol_optional]: number } +interface optionalToString { toString(): Returns>, [symbol.optional]: number } interface recordToString { toString(): Returns> } interface unionToString { toString(): Returns> } interface intersectToString { toString(): Returns> } @@ -150,3 +146,4 @@ function unionToString(this: t.union) { return toString.union(this.def) } function intersectToString(this: t.intersect) { return toString.intersect(this.def) } function tupleToString(this: t.tuple) { return toString.tuple(this.def) } function objectToString(this: t.object) { return toString.object(this.def) } + diff --git a/packages/schema-to-string/test/install.test.ts b/packages/schema-to-string/test/install.test.ts index b7c2622e..39bc6ab1 100644 --- a/packages/schema-to-string/test/install.test.ts +++ b/packages/schema-to-string/test/install.test.ts @@ -1,5 +1,5 @@ import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-to-string❳', () => { vi.it('〖⛳️〗› ❲pre-install❳', () => { diff --git a/packages/schema-to-string/test/integration.test.ts b/packages/schema-to-string/test/integration.test.ts index 1356474c..7485770d 100644 --- a/packages/schema-to-string/test/integration.test.ts +++ b/packages/schema-to-string/test/integration.test.ts @@ -3,7 +3,7 @@ import { fc } from '@fast-check/vitest' import * as path from 'node:path' import * as fs from 'node:fs' -import { recurse } from '@traversable/schema' +import { recurse } from '@traversable/schema-core' import { Seed } from '@traversable/schema-seed' import '@traversable/schema-to-string/install' @@ -38,12 +38,10 @@ if (!fs.existsSync(PATH.dir)) fs.mkdirSync(PATH.dir) if (!fs.existsSync(PATH.target.schemas)) fs.writeFileSync(PATH.target.schemas, '') if (!fs.existsSync(PATH.target.toString)) fs.writeFileSync(PATH.target.toString, '') -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: integration tests', () => { - // void bindToStrings() - +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳: integration tests', () => { const imports = [ `import * as vi from 'vitest'`, - `import { t } from '@traversable/schema'` + `import { t } from '@traversable/schema-core'` ] as const satisfies string[] const gen = fc.sample(Seed.schema(OPTIONS), NUM_RUNS) @@ -95,7 +93,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: integration tests', fs.writeFileSync(PATH.target.schemas, schemasOut) fs.writeFileSync(PATH.target.toString, toStringsOut) - vi.it('〖⛳️〗› ❲@traverable/schema❳: it writes', () => { + vi.it('〖⛳️〗› ❲@traversable/schema-core❳: it writes', () => { vi.assert.isTrue(fs.existsSync(PATH.target.schemas)) vi.assert.isTrue(fs.existsSync(PATH.target.toString)) }) diff --git a/packages/schema-to-string/test/toString.test.ts b/packages/schema-to-string/test/toString.test.ts index f088f545..aff5de42 100644 --- a/packages/schema-to-string/test/toString.test.ts +++ b/packages/schema-to-string/test/toString.test.ts @@ -1,6 +1,6 @@ import * as vi from 'vitest' -import { t, configure } from '@traversable/schema' +import { t, configure } from '@traversable/schema-core' import '@traversable/schema-to-string/install' configure({ @@ -9,7 +9,7 @@ configure({ } }) -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳: t.recurse.toString', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core❳: t.recurse.toString', () => { vi.it('〖⛳️〗‹ ❲t.recurse.toString(t.void)❳', () => vi.expect(t.recurse.toString(t.void)).toMatchInlineSnapshot(`"t.void"`)) vi.it('〖⛳️〗‹ ❲t.recurse.toString(t.any)❳', () => vi.expect(t.recurse.toString(t.any)).toMatchInlineSnapshot(`"t.any"`)) vi.it('〖⛳️〗‹ ❲t.recurse.toString(t.unknown)❳', () => vi.expect(t.recurse.toString(t.unknown)).toMatchInlineSnapshot(`"t.unknown"`)) @@ -306,13 +306,13 @@ vi.it('〖⛳️〗› ❲t.enum(...)❳', () => { vi.expect(ex_02).toMatchInlineSnapshot(`"undefined | null | symbol | true | 1n | 1 | '\\'"`) vi.assertType<`undefined | null | false | symbol | 0 | 0n | ''`>(ex_01) vi.assertType< - | `undefined | null | true | symbol | 1 | 1n | '\\'` | `'\\' | undefined | null | true | symbol | 1 | 1n` + | `1 | undefined | null | true | symbol | 1n | '\\'` | `undefined | null | true | symbol | 1 | '\\' | 1n` + | `undefined | null | true | symbol | 1 | 1n | '\\'` >(ex_02) }) - vi.it('〖⛳️〗› ❲t.tuple(...).toString❳', () => { vi.expect(t.tuple().toString()).toMatchInlineSnapshot(`"[]"`) vi.assertType<'[]'>(t.tuple().toString()) @@ -506,6 +506,7 @@ vi.it('〖⛳️〗› ❲t.object(...).toString❳', () => ( ).toMatchInlineSnapshot(`"{ 'a': { 'b': { 'c'?: ('a.b.c' | undefined), 'd': 'a.b.d' }, 'e'?: ({ 'f': 'a.e.f', 'g'?: ('a.e.g' | undefined) } | undefined) }, 'h'?: ({ 'i'?: ({ 'j': 'h.i.j', 'k'?: ('h.i.k' | undefined) } | undefined), 'l': { 'm'?: ('h.l.m' | undefined), 'n': 'h.l.n' } } | undefined) }"`), vi.assertType< + | `{ 'a': { 'b': { 'c': ('a.b.c' | undefined), 'd': 'a.b.d' }, 'e': ({ 'f': 'a.e.f', 'g': ('a.e.g' | undefined) } | undefined) }, 'h': ({ 'i': ({ 'j': 'h.i.j', 'k': ('h.i.k' | undefined) } | undefined), 'l': { 'm': ('h.l.m' | undefined), 'n': 'h.l.n' } } | undefined) }` | `{ 'a': { 'b': { 'c'?: ('a.b.c' | undefined), 'd': 'a.b.d' }, 'e'?: ({ 'f': 'a.e.f', 'g'?: ('a.e.g' | undefined) } | undefined) }, 'h'?: ({ 'i'?: ({ 'j': 'h.i.j', 'k'?: ('h.i.k' | undefined) } | undefined), 'l': { 'm'?: ('h.l.m' | undefined), 'n': 'h.l.n' } } | undefined) }` | `{ 'a': { 'b': { 'c'?: ('a.b.c' | undefined), 'd': 'a.b.d' }, 'e'?: ({ 'f': 'a.e.f', 'g'?: ('a.e.g' | undefined) } | undefined) }, 'h'?: ({ 'i'?: ({ 'k'?: ('h.i.k' | undefined), 'j': 'h.i.j' } | undefined), 'l': { 'm'?: ('h.l.m' | undefined), 'n': 'h.l.n' } } | undefined) }` | `{ 'a': { 'b': { 'c'?: ('a.b.c' | undefined), 'd': 'a.b.d' }, 'e'?: ({ 'f': 'a.e.f', 'g'?: ('a.e.g' | undefined) } | undefined) }, 'h'?: ({ 'l': { 'm'?: ('h.l.m' | undefined), 'n': 'h.l.n' }, 'i'?: ({ 'k'?: ('h.i.k' | undefined), 'j': 'h.i.j' } | undefined) } | undefined) }` @@ -1044,6 +1045,7 @@ vi.it('〖⛳️〗› ❲t.object.toString❳: stress tests', () => { ).toMatchInlineSnapshot(`"{ 'one': { 'one': 1, 'two': { 'one': 1, 'two': 2, 'three': { 'one': 1, 'two': 2, 'three': 3, 'four': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': { 'one': 1, 'two': 2, 'three': 3, 'four': 4, 'five': 5, 'six': 6, 'seven': 7, 'eight': 8, 'nine': 9, 'ten': 10, 'eleven': 11, 'twelve': 12, 'thirteen': 13, 'fourteen': 14, 'fifteen': {} }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500, 'six_hundred': 600, 'seven_hundred': 700, 'eight_hundred': 800, 'nine_hundred': 900, 'one_thousand': 1000, 'eleven_hundred': 1100, 'twelve_hundred': 1200, 'thirteen_hundred': 1300, 'fourteen_hundred': 1400 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500, 'six_hundred': 600, 'seven_hundred': 700, 'eight_hundred': 800, 'nine_hundred': 900, 'one_thousand': 1000, 'eleven_hundred': 1100, 'twelve_hundred': 1200, 'thirteen_hundred': 1300 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500, 'six_hundred': 600, 'seven_hundred': 700, 'eight_hundred': 800, 'nine_hundred': 900, 'one_thousand': 1000, 'eleven_hundred': 1100, 'twelve_hundred': 1200 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500, 'six_hundred': 600, 'seven_hundred': 700, 'eight_hundred': 800, 'nine_hundred': 900, 'one_thousand': 1000, 'eleven_hundred': 1100 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500, 'six_hundred': 600, 'seven_hundred': 700, 'eight_hundred': 800, 'nine_hundred': 900, 'one_thousand': 1000 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500, 'six_hundred': 600, 'seven_hundred': 700, 'eight_hundred': 800, 'nine_hundred': 900 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500, 'six_hundred': 600, 'seven_hundred': 700, 'eight_hundred': 800 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500, 'six_hundred': 600, 'seven_hundred': 700 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500, 'six_hundred': 600 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400, 'five_hundred': 500 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300, 'four_hundred': 400 }, 'one_hundred': 100, 'two_hundred': 200, 'three_hundred': 300 }, 'one_hundred': 100, 'two_hundred': 200 }, 'one_hundred': 100 }"`) vi.assertType< + | `{ '1': ['3'], '2': ['3'], '0.2': { '22': ['3', '+', '30'] }, '3': ['3'], '0.3': { '33': ['3', '+', '30'], '0.33': { '333': ['3', '+', '30', '300'] } }, '4': ['4'], '0.4': { '44': ['4', '+', '40'], '0.44': { '444': ['4', '+', '40', '400'], '0.444': { '4444': ['4', '+', '40', '+', '400', '+', '4000'] } } }, '5': ['5'], '0.5': { '55': ['5', '+', '50'], '0.55': { '555': ['5', '+', '50', '500'], '0.555': { '5555': ['5', '+', '50', '+', '500', '+', '5000'], '0.5555': { '55555': ['5', '+', '50', '+', '500', '+', '5000', '+', '50000'] } } } }, '6': ['6'], '0.6': { '66': ['6', '+', '60'], '0.66': { '666': ['6', '+', '60', '600'], '0.666': { '6666': ['6', '+', '60', '+', '600', '+', '6000'], '0.6666': { '66666': ['6', '+', '60', '+', '600', '+', '6000', '+', '60000'], '0.66666': { '666666': ['6', '+', '60', '+', '600', '+', '6000', '+', '60000', '+', '600000'] } } } } }, '7': ['7'], '0.7': { '77': ['7', '+', '70'], '0.77': { '777': ['7', '+', '70', '700'], '0.777': { '7777': ['7', '+', '70', '+', '700', '+', '7000'], '0.7777': { '77777': ['7', '+', '70', '+', '700', '+', '7000', '+', '70000'], '0.77777': { '777777': ['7', '+', '70', '+', '700', '+', '7000', '+', '70000', '+', '700000'], '0.777777': { '7777777': ['7', '+', '70', '+', '700', '+', '7000', '+', '70000', '+', '700000', '+', '7000000'] } } } } } }, '8': ['8'], '0.8': { '88': ['8', '+', '80'], '0.88': { '888': ['8', '+', '80', '800'], '0.888': { '8888': ['8', '+', '80', '+', '800', '+', '8000'], '0.8888': { '88888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000'], '0.88888': { '888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000'], '0.888888': { '8888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000', '+', '8000000'], '0.8888888': { '88888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000', '+', '8000000', '+', '80000000'], '0.88888888': { '888888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000', '+', '8000000', '+', '80000000', '+', '800000000'] } } } } } } } }, '9': ['9'], '0.9': { '99': ['9', '+', '90'], '0.99': { '999': ['9', '+', '90', '900'], '0.999': { '9999': ['9', '+', '90', '+', '900', '+', '9000'], '0.9999': { '99999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000'], '0.99999': { '999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000'], '0.999999': { '9999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000'], '0.9999999': { '99999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000', '+', '90000000'], '0.99999999': { '999999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000', '+', '90000000', '+', '900000000'], '0.999999999': { '9999999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000', '+', '90000000', '+', '900000000', '+', '9000000000'] } } } } } } } } } }` | `{ '1': ['3'], '2': ['3'], '3': ['3'], '4': ['4'], '5': ['5'], '6': ['6'], '7': ['7'], '8': ['8'], '9': ['9'], '0.2': { '22': ['3', '+', '30'] }, '0.3': { '33': ['3', '+', '30'], '0.33': { '333': ['3', '+', '30', '300'] } }, '0.4': { '44': ['4', '+', '40'], '0.44': { '444': ['4', '+', '40', '400'], '0.444': { '4444': ['4', '+', '40', '+', '400', '+', '4000'] } } }, '0.5': { '55': ['5', '+', '50'], '0.55': { '555': ['5', '+', '50', '500'], '0.555': { '5555': ['5', '+', '50', '+', '500', '+', '5000'], '0.5555': { '55555': ['5', '+', '50', '+', '500', '+', '5000', '+', '50000'] } } } }, '0.6': { '66': ['6', '+', '60'], '0.66': { '666': ['6', '+', '60', '600'], '0.666': { '6666': ['6', '+', '60', '+', '600', '+', '6000'], '0.6666': { '66666': ['6', '+', '60', '+', '600', '+', '6000', '+', '60000'], '0.66666': { '666666': ['6', '+', '60', '+', '600', '+', '6000', '+', '60000', '+', '600000'] } } } } }, '0.7': { '77': ['7', '+', '70'], '0.77': { '777': ['7', '+', '70', '700'], '0.777': { '7777': ['7', '+', '70', '+', '700', '+', '7000'], '0.7777': { '77777': ['7', '+', '70', '+', '700', '+', '7000', '+', '70000'], '0.77777': { '777777': ['7', '+', '70', '+', '700', '+', '7000', '+', '70000', '+', '700000'], '0.777777': { '7777777': ['7', '+', '70', '+', '700', '+', '7000', '+', '70000', '+', '700000', '+', '7000000'] } } } } } }, '0.8': { '88': ['8', '+', '80'], '0.88': { '888': ['8', '+', '80', '800'], '0.888': { '8888': ['8', '+', '80', '+', '800', '+', '8000'], '0.8888': { '88888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000'], '0.88888': { '888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000'], '0.888888': { '8888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000', '+', '8000000'], '0.8888888': { '88888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000', '+', '8000000', '+', '80000000'], '0.88888888': { '888888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000', '+', '8000000', '+', '80000000', '+', '800000000'] } } } } } } } }, '0.9': { '99': ['9', '+', '90'], '0.99': { '999': ['9', '+', '90', '900'], '0.999': { '9999': ['9', '+', '90', '+', '900', '+', '9000'], '0.9999': { '99999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000'], '0.99999': { '999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000'], '0.999999': { '9999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000'], '0.9999999': { '99999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000', '+', '90000000'], '0.99999999': { '999999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000', '+', '90000000', '+', '900000000'], '0.999999999': { '9999999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000', '+', '90000000', '+', '900000000', '+', '9000000000'] } } } } } } } } } }` | `{ '5': ['5'], '9': ['9'], '8': ['8'], '1': ['3'], '2': ['3'], '3': ['3'], '4': ['4'], '6': ['6'], '7': ['7'], '0.2': { '22': ['3', '+', '30'] }, '0.3': { '33': ['3', '+', '30'], '0.33': { '333': ['3', '+', '30', '300'] } }, '0.4': { '44': ['4', '+', '40'], '0.44': { '444': ['4', '+', '40', '400'], '0.444': { '4444': ['4', '+', '40', '+', '400', '+', '4000'] } } }, '0.5': { '55': ['5', '+', '50'], '0.55': { '555': ['5', '+', '50', '500'], '0.555': { '5555': ['5', '+', '50', '+', '500', '+', '5000'], '0.5555': { '55555': ['5', '+', '50', '+', '500', '+', '5000', '+', '50000'] } } } }, '0.6': { '66': ['6', '+', '60'], '0.66': { '666': ['6', '+', '60', '600'], '0.666': { '6666': ['6', '+', '60', '+', '600', '+', '6000'], '0.6666': { '66666': ['6', '+', '60', '+', '600', '+', '6000', '+', '60000'], '0.66666': { '666666': ['6', '+', '60', '+', '600', '+', '6000', '+', '60000', '+', '600000'] } } } } }, '0.7': { '77': ['7', '+', '70'], '0.77': { '777': ['7', '+', '70', '700'], '0.777': { '7777': ['7', '+', '70', '+', '700', '+', '7000'], '0.7777': { '77777': ['7', '+', '70', '+', '700', '+', '7000', '+', '70000'], '0.77777': { '777777': ['7', '+', '70', '+', '700', '+', '7000', '+', '70000', '+', '700000'], '0.777777': { '7777777': ['7', '+', '70', '+', '700', '+', '7000', '+', '70000', '+', '700000', '+', '7000000'] } } } } } }, '0.8': { '88': ['8', '+', '80'], '0.88': { '888': ['8', '+', '80', '800'], '0.888': { '8888': ['8', '+', '80', '+', '800', '+', '8000'], '0.8888': { '88888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000'], '0.88888': { '888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000'], '0.888888': { '8888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000', '+', '8000000'], '0.8888888': { '88888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000', '+', '8000000', '+', '80000000'], '0.88888888': { '888888888': ['8', '+', '80', '+', '800', '+', '8000', '+', '80000', '+', '800000', '+', '8000000', '+', '80000000', '+', '800000000'] } } } } } } } }, '0.9': { '99': ['9', '+', '90'], '0.99': { '999': ['9', '+', '90', '900'], '0.999': { '9999': ['9', '+', '90', '+', '900', '+', '9000'], '0.9999': { '99999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000'], '0.99999': { '999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000'], '0.999999': { '9999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000'], '0.9999999': { '99999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000', '+', '90000000'], '0.99999999': { '999999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000', '+', '90000000', '+', '900000000'], '0.999999999': { '9999999999': ['9', '+', '90', '+', '900', '+', '9000', '+', '90000', '+', '900000', '+', '9000000', '+', '90000000', '+', '900000000', '+', '9000000000'] } } } } } } } } } }` >( diff --git a/packages/schema-to-string/test/zod-side-by-side.test.ts b/packages/schema-to-string/test/zod-side-by-side.test.ts index 8759d1be..03c21023 100644 --- a/packages/schema-to-string/test/zod-side-by-side.test.ts +++ b/packages/schema-to-string/test/zod-side-by-side.test.ts @@ -1,5 +1,5 @@ import * as vi from 'vitest' -import { t } from '@traversable/schema' +import { t } from '@traversable/schema-core' import { z } from 'zod' /** @@ -1191,7 +1191,7 @@ const ZodSchema = z.object({ -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema-to-string❳', () => { +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-to-string❳', () => { vi.it('〖⛳️〗‹ ❲side-by-side❳: zod', () => { /** diff --git a/packages/schema-to-string/tsconfig.build.json b/packages/schema-to-string/tsconfig.build.json index cabdfce7..2972409b 100644 --- a/packages/schema-to-string/tsconfig.build.json +++ b/packages/schema-to-string/tsconfig.build.json @@ -9,6 +9,6 @@ }, "references": [ { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ] } diff --git a/packages/schema-to-string/tsconfig.src.json b/packages/schema-to-string/tsconfig.src.json index fa90f525..9416bd78 100644 --- a/packages/schema-to-string/tsconfig.src.json +++ b/packages/schema-to-string/tsconfig.src.json @@ -8,7 +8,7 @@ }, "references": [ { "path": "../registry" }, - { "path": "../schema" } + { "path": "../schema-core" } ], "include": ["src"] } diff --git a/packages/schema-to-string/tsconfig.test.json b/packages/schema-to-string/tsconfig.test.json index 7ac34fc6..3ad0d988 100644 --- a/packages/schema-to-string/tsconfig.test.json +++ b/packages/schema-to-string/tsconfig.test.json @@ -9,8 +9,8 @@ "references": [ { "path": "tsconfig.src.json" }, { "path": "../registry" }, - { "path": "../schema" }, - { "path": "../schema-seed" }, + { "path": "../schema-core" }, + { "path": "../schema-seed" } ], "include": ["test"] } diff --git a/packages/schema/src/clone.ts b/packages/schema/src/clone.ts deleted file mode 100644 index c954f122..00000000 --- a/packages/schema/src/clone.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Param } from '@traversable/registry' -import type { LowerBound } from './schema.js' - -export function clone(schema: S): S -export function clone(schema: S) { - function cloned(u: Param) { return schema(u) } - for (const k in schema) (cloned as typeof schema)[k] = schema[k] - return cloned -} - -// export function extend(): (schema: S, extension: Partial) => Ext -// export function extend() { -// return (schema: S, extension: Ext) => { -// function cloned(u: Param) { return schema(u) } -// for (const k in schema) (cloned as S)[k] = schema[k] -// for (const k in extension) (cloned as Ext)[k] = extension[k] -// return cloned -// } -// } diff --git a/packages/schema/src/extensions.ts b/packages/schema/src/extensions.ts deleted file mode 100644 index 23f2ac3d..00000000 --- a/packages/schema/src/extensions.ts +++ /dev/null @@ -1,32 +0,0 @@ -export type { - LowerBound as t_LowerBound, - Schema as t_Schema, -} from './schema.js' -export { - enum as t_enum, -} from './enum.js' - -export { - never as t_never, - unknown as t_unknown, - void as t_void, - any as t_any, - null as t_null, - undefined as t_undefined, - symbol as t_symbol, - boolean as t_boolean, - integer as t_integer, - bigint as t_bigint, - number as t_number, - string as t_string, - eq as t_eq, - optional as t_optional, - array as t_array, - record as t_record, - object as t_object, - tuple as t_tuple, - union as t_union, - intersect as t_intersect, - of as t_of, - -} from './schema.js' diff --git a/packages/schema/src/namespace.ts b/packages/schema/src/namespace.ts deleted file mode 100644 index 3a358da8..00000000 --- a/packages/schema/src/namespace.ts +++ /dev/null @@ -1,80 +0,0 @@ -export * as recurse from './recursive.js' - -export type { - bottom, - Boundable, - Entry, - F, - Fixpoint, - Free, - Inline, - invalid, - Leaf, - LowerBound, - Predicate, - Optional, - Required, - Schema, - Tag, - top, - typeOf as typeof, - Unspecified, -} from './schema.js' - -export { - isLeaf, - isNullary, - isNullaryTag, - isBoundable, - isBoundableTag, - isPredicate, - isUnary, - isCore, - Functor, - IndexedFunctor, - fold, - foldWithIndex, - unfold, - tags, -} from './schema.js' -export type { Typeguard } from './types.js' - -export { key, has } from './has.js' - -/* data-types & combinators */ -export * from './combinators.js' -export { enum } from './enum.js' -export { - never, - unknown, - any, - void, - null, - undefined, - symbol, - boolean, - integer, - of, - bigint, - number, - string, - nonnullable, - eq, - optional, - array, - readonlyArray, - record, - union, - intersect, - tuple, - object, -} from './schema.js' - -/** - * exported as escape hatches, to prevent collisions with built-in keywords - */ -export type { - typeOf as typeof_, - null_, - void_, -} from './schema.js' diff --git a/packages/schema/test/combinators.test.ts b/packages/schema/test/combinators.test.ts deleted file mode 100644 index 6e0c1907..00000000 --- a/packages/schema/test/combinators.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -import * as vi from 'vitest' -import { t } from '@traversable/schema' -import { fc, test } from '@fast-check/vitest' -import * as Seed from './seed.js' - -vi.describe('〖⛳️〗‹‹‹ ❲@traverable/schema❳', () => { - const natural = t.filter(t.integer, x => x >= 0) - const varchar = t.filter(t.string)(x => 0x100 >= x.length) - - const arbitrary = fc.letrec(Seed.seed()).tree.chain((seed) => fc.constant([ - Seed.toSchema(seed), - fc.constant(Seed.toJson(seed)), - ] satisfies [any, any])) - - vi.describe('〖⛳️〗‹‹ ❲t.filter❳', () => { - test.prop([fc.nat()])( - '〖⛳️〗‹ ❲t.filter(t.integer, q)❳: returns true when `q` is satisied', - (x) => vi.assert.isTrue(natural(x)) - ) - test.prop([fc.nat().map((x) => -x).filter((x) => x !== 0)])( - '〖⛳️〗‹ ❲t.filter(t.integer, q)❳: returns false when `q` is not satisfied', - (x) => vi.assert.isFalse(natural(x)) - ) - - test.prop([fc.string({ maxLength: 0x100 })])( - '〖⛳️〗‹ ❲t.filter(t.string, q)❳: returns true when `q` is satisfied', - (x) => vi.assert.isTrue(varchar(x)) - ) - test.prop([fc.string({ minLength: 0x101 })], {})( - '〖⛳️〗‹ ❲t.filter(t.string, q)❳: returns false when `q` is not satisfied', - (x) => vi.assert.isFalse(varchar(x)) - ) - - test.prop([arbitrary, fc.func(fc.boolean())])( - /** - * See also: - * https://www.wisdom.weizmann.ac.il/~/oded/VO/mono1.pdf - */ - '〖⛳️〗‹ ❲t.filter(s, q)❳: is monotone cf. s ∩ q', - ([s, x], q) => vi.assert.equal(t.filter(s, q)(x), s(x) && q(x)) - ) - }) -}) diff --git a/packages/schema/test/fast-check.ts b/packages/schema/test/fast-check.ts deleted file mode 100644 index f99d3983..00000000 --- a/packages/schema/test/fast-check.ts +++ /dev/null @@ -1,92 +0,0 @@ -export * from 'fast-check' -import * as fc from 'fast-check' - -import { symbol as Symbol } from '@traversable/registry' -import type { Guard } from '@traversable/schema' - -export interface Arbitrary extends fc.Arbitrary { - readonly [Symbol.optional]?: true -} - -export type { typeOf as typeof } -type typeOf = S extends fc.Arbitrary ? T : never - -/** @internal */ -const Object_keys = globalThis.Object.keys -/** @internal */ -const Array_isArray = globalThis.Array.isArray -/** @internal */ -const isString: Guard = (u): u is never => typeof u === 'string' -/** @internal */ -const arrayOf - : (p: Guard) => Guard - = (p) => (u): u is never => Array_isArray(u) && u.every(p) -/** @internal */ -const has - : (k: K, p: Guard) => Guard<{ [P in K]: T }> - = (k, p) => (u: unknown): u is never => - !!u && - typeof u === 'object' && - Object.prototype.hasOwnProperty.call(u, k) && - p(u[k as never]) - -/** @internal */ -// const KEYWORD = { -// break: 'break', case: 'case', catch: 'catch', class: 'class', const: 'const', -// continue: 'continue', debugger: 'debugger', default: 'default', delete: 'delete', -// do: 'do', else: 'else', export: 'export', extends: 'extends', false: 'false', -// finally: 'finally', for: 'for', function: 'function', if: 'if', import: 'import', -// in: 'in', instanceof: 'instanceof', new: 'new', null: 'null', return: 'return', -// super: 'super', switch: 'switch', this: 'this', throw: 'throw', true: 'true', -// try: 'try', typeof: 'typeof', var: 'var', void: 'void', while: 'while', -// with: 'with', let: 'let', static: 'static', yield: 'yield', -// } as const satisfies Record - -// const Keywords = [ -// 'break', 'case', 'catch', 'class', 'const', -// 'continue', 'debugger', 'default', 'delete', -// 'do', 'else', 'export', 'extends', 'false', -// 'finally', 'for', 'function', 'if', 'import', -// 'in', 'instanceof', 'new', 'null', 'return', -// 'super', 'switch', 'this', 'throw', 'true', -// 'try', 'typeof', 'var', 'void', 'while', -// 'with', 'let', 'static', 'yield', -// ] - -const PATTERN = { - identifier: '^[$_a-zA-Z][$_a-zA-Z0-9]*$', - // identifierSpec: '[$_\\p{ID_Start}][$_\\u200C\\u200D\\p{ID_Continue}]*', -} as const - -const REGEX = { - identifier: new globalThis.RegExp(PATTERN.identifier, 'u'), - // identifierSpec: new globalThis.RegExp(PATTERN.identifierSpec, 'u'), -} as const - -export type UniqueArrayDefaults = fc.UniqueArrayConstraintsRecommended - -/** - * See also: - * - the MDN docs on - * [identifiers in JavaScript](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#identifiers) - */ -export function identifier(constraints?: fc.StringMatchingConstraints): fc.Arbitrary -export function identifier(constraints?: fc.StringMatchingConstraints) { - return fc.stringMatching(REGEX.identifier, constraints) //.filter((ident) => !(ident in KEYWORD)) -} - -export const entries - : (model: fc.Arbitrary, constraints?: UniqueArrayDefaults) => fc.Arbitrary<[k: string, v: T][]> - = (model, constraints) => fc.uniqueArray( - fc.tuple(identifier(), model), - { ...constraints, selector: ([k]) => k }, - ) - -/** - * ### {@link optional `fc.optional`} - */ -export function optional(model: fc.Arbitrary, constraints?: fc.OneOfConstraints): Arbitrary -export function optional(model: fc.Arbitrary, _constraints: fc.OneOfConstraints = {}): fc.Arbitrary { - (model as any)[Symbol.optional] = true; - return model -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ac3945d..00f3f336 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,9 +114,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../../packages/registry/dist - '@traversable/schema': + '@traversable/schema-core': specifier: workspace:^ - version: link:../../packages/schema/dist + version: link:../../packages/schema-core/dist '@traversable/schema-seed': specifier: workspace:^ version: link:../../packages/schema-seed/dist @@ -178,9 +178,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist - '@traversable/schema': + '@traversable/schema-core': specifier: workspace:^ - version: link:../schema/dist + version: link:../schema-core/dist '@traversable/schema-seed': specifier: workspace:^ version: link:../schema-seed/dist @@ -194,9 +194,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist - '@traversable/schema': + '@traversable/schema-core': specifier: workspace:^ - version: link:../schema/dist + version: link:../schema-core/dist '@traversable/schema-seed': specifier: workspace:^ version: link:../schema-seed/dist @@ -213,9 +213,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist - '@traversable/schema': + '@traversable/schema-core': specifier: workspace:^ - version: link:../schema/dist + version: link:../schema-core/dist '@traversable/schema-seed': specifier: workspace:^ version: link:../schema-seed/dist @@ -235,7 +235,7 @@ importers: packages/registry: publishDirectory: dist - packages/schema: + packages/schema-core: dependencies: '@traversable/registry': specifier: workspace:^ @@ -269,9 +269,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist - '@traversable/schema': + '@traversable/schema-core': specifier: workspace:^ - version: link:../schema/dist + version: link:../schema-core/dist '@traversable/schema-to-json-schema': specifier: workspace:^ version: link:../schema-to-json-schema/dist @@ -291,9 +291,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist - '@traversable/schema': + '@traversable/schema-core': specifier: workspace:^ - version: link:../schema/dist + version: link:../schema-core/dist fast-check: specifier: ^3.23.2 version: 3.23.2 @@ -304,9 +304,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist - '@traversable/schema': + '@traversable/schema-core': specifier: workspace:^ - version: link:../schema/dist + version: link:../schema-core/dist '@traversable/schema-seed': specifier: workspace:^ version: link:../schema-seed/dist @@ -317,9 +317,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist - '@traversable/schema': + '@traversable/schema-core': specifier: workspace:^ - version: link:../schema/dist + version: link:../schema-core/dist '@traversable/schema-seed': specifier: workspace:^ version: link:../schema-seed/dist diff --git a/tsconfig.base.json b/tsconfig.base.json index bc6edfe5..aac589e7 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -32,44 +32,26 @@ "@traversable/derive-codec/*": ["packages/derive-codec/src/*.js"], "@traversable/derive-equals": ["packages/derive-equals/src/index.js"], "@traversable/derive-equals/*": ["packages/derive-equals/src/*.js"], - "@traversable/derive-validators": [ - "packages/derive-validators/src/index.js" - ], - "@traversable/derive-validators/*": [ - "packages/derive-validators/src/*.js" - ], + "@traversable/derive-validators": [ "packages/derive-validators/src/index.js" ], + "@traversable/derive-validators/*": [ "packages/derive-validators/src/*.js" ], "@traversable/json": ["packages/json/src/index.js"], "@traversable/json/*": ["packages/json/src/*.js"], "@traversable/registry": ["packages/registry/src/index.js"], "@traversable/registry/*": ["packages/registry/src/*.js"], - "@traversable/schema": ["packages/schema/src/index.js"], - "@traversable/schema-generator": [ - "packages/schema-generator/src/index.js" - ], + "@traversable/schema-core": ["packages/schema-core/src/index.js"], + "@traversable/schema-core/*": ["packages/schema-core/src/*.js"], + "@traversable/schema-generator": [ "packages/schema-generator/src/index.js" ], "@traversable/schema-generator/*": ["packages/schema-generator/*.js"], "@traversable/schema-seed": ["packages/schema-seed/src/index.js"], "@traversable/schema-seed/*": ["packages/schema-seed/src/*.js"], - "@traversable/schema-to-json-schema": [ - "packages/schema-to-json-schema/src/index.js" - ], - "@traversable/schema-to-json-schema/*": [ - "packages/schema-to-json-schema/src/*.js" - ], - "@traversable/schema-to-string": [ - "packages/schema-to-string/src/index.js" - ], - "@traversable/schema-to-string/*": ["packages/schema-to-string/src/*.js"], - "@traversable/schema-valibot-adapter": [ - "packages/schema-valibot-adapter/src/index.js" - ], - "@traversable/schema-valibot-adapter/*": [ - "packages/schema-valibot-adapter/src/*.js" - ], - "@traversable/schema-zod-adapter": [ - "packages/schema-zod-adapter/src/index.js" - ], - "@traversable/schema-zod-adapter/*": ["packages/schema-zod-adapter/*.js"], - "@traversable/schema/*": ["packages/schema/src/*.js"] + "@traversable/schema-to-json-schema": [ "packages/schema-to-json-schema/src/index.js" ], + "@traversable/schema-to-json-schema/*": [ "packages/schema-to-json-schema/src/*.js" ], + "@traversable/schema-to-string": [ "packages/schema-to-string/src/index.js" ], + "@traversable/schema-to-string/*": ["packages/schema-to-string/src/*.js"], + "@traversable/schema-valibot-adapter": [ "packages/schema-valibot-adapter/src/index.js" ], + "@traversable/schema-valibot-adapter/*": [ "packages/schema-valibot-adapter/src/*.js" ], + "@traversable/schema-zod-adapter": [ "packages/schema-zod-adapter/src/index.js" ], + "@traversable/schema-zod-adapter/*": ["packages/schema-zod-adapter/*.js"] } } } diff --git a/tsconfig.build.json b/tsconfig.build.json index 68c7ae96..394ead0b 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -7,12 +7,12 @@ { "path": "packages/derive-validators/tsconfig.build.json" }, { "path": "packages/json/tsconfig.build.json" }, { "path": "packages/registry/tsconfig.build.json" }, + { "path": "packages/schema-core/tsconfig.build.json" }, { "path": "packages/schema-generator/tsconfig.build.json" }, { "path": "packages/schema-seed/tsconfig.build.json" }, { "path": "packages/schema-to-json-schema/tsconfig.build.json" }, { "path": "packages/schema-to-string/tsconfig.build.json" }, { "path": "packages/schema-valibot-adapter/tsconfig.build.json" }, { "path": "packages/schema-zod-adapter/tsconfig.build.json" }, - { "path": "packages/schema/tsconfig.build.json" } ] } diff --git a/tsconfig.json b/tsconfig.json index 7289c45c..a71403c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,11 +7,11 @@ { "path": "packages/derive-validators" }, { "path": "packages/json" }, { "path": "packages/registry" }, - { "path": "packages/schema" }, { "path": "packages/schema-generator" }, { "path": "packages/schema-seed" }, { "path": "packages/schema-to-json-schema" }, { "path": "packages/schema-to-string" }, + { "path": "packages/schema-core" }, { "path": "packages/schema-valibot-adapter" }, { "path": "packages/schema-zod-adapter" } ] diff --git a/vite.config.ts b/vite.config.ts index e2f0adb1..f2b4632d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -25,7 +25,7 @@ export default defineConfig({ alias: ALIASES, coverage: { include: [ - 'packages/schema/src/**.ts', + 'packages/schema-core/src/**.ts', ], enabled: true, reporter: ['html'], From bae9a486b41228387656d826ec460c59ad6d7af0 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 12 Apr 2025 11:40:12 -0500 Subject: [PATCH 24/45] sure --- packages/schema-core/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema-core/README.md b/packages/schema-core/README.md index 63da4390..837ae714 100644 --- a/packages/schema-core/README.md +++ b/packages/schema-core/README.md @@ -1,5 +1,5 @@
-

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘀𝗰𝗵𝗲𝗺𝗮-𝗰𝗼𝗿𝗲

+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘀𝗰𝗵𝗲𝗺𝗮


From 04d76663beb382316f5d3b545d5aa6ab432807e9 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 12 Apr 2025 11:47:12 -0500 Subject: [PATCH 25/45] build(generator): runs cli from package.json --- packages/schema-generator/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema-generator/package.json b/packages/schema-generator/package.json index e1929b39..cf55500c 100644 --- a/packages/schema-generator/package.json +++ b/packages/schema-generator/package.json @@ -42,7 +42,7 @@ "clean": "pnpm run \"/^clean:.*/\"", "clean:build": "rm -rf .tsbuildinfo dist build", "clean:deps": "rm -rf node_modules", - "postinstall": "./src/cli.ts", + "postinstall": "pnpm dlx tsx ./src/cli.ts", "test": "vitest" }, "peerDependencies": { From 3ca3de1652bfcb19187d0fb20c8d69bd15a6ac97 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 12 Apr 2025 11:49:54 -0500 Subject: [PATCH 26/45] chore(generator): commits generated file --- packages/schema-generator/src/__generated__/__manifest__.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/schema-generator/src/__generated__/__manifest__.ts b/packages/schema-generator/src/__generated__/__manifest__.ts index f54f124d..31647f52 100644 --- a/packages/schema-generator/src/__generated__/__manifest__.ts +++ b/packages/schema-generator/src/__generated__/__manifest__.ts @@ -38,7 +38,7 @@ export default { "clean": "pnpm run \"/^clean:.*/\"", "clean:build": "rm -rf .tsbuildinfo dist build", "clean:deps": "rm -rf node_modules", - "postinstall": "./src/cli.ts", + "postinstall": "pnpm dlx tsx ./src/cli.ts", "test": "vitest" }, "peerDependencies": { From 80c180960fabe7803350691c2af5760474097bd1 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 12 Apr 2025 12:23:00 -0500 Subject: [PATCH 27/45] fix(built): dangling comma --- tsconfig.build.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tsconfig.build.json b/tsconfig.build.json index 394ead0b..d13e1e62 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -13,6 +13,6 @@ { "path": "packages/schema-to-json-schema/tsconfig.build.json" }, { "path": "packages/schema-to-string/tsconfig.build.json" }, { "path": "packages/schema-valibot-adapter/tsconfig.build.json" }, - { "path": "packages/schema-zod-adapter/tsconfig.build.json" }, + { "path": "packages/schema-zod-adapter/tsconfig.build.json" } ] } From 8bc7099dfee06284fdde11f11aa220b0d980e481 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 12 Apr 2025 22:12:00 -0500 Subject: [PATCH 28/45] feat(schema): generating from core :tada: --- README.md | 9 +- config/__generated__/package-list.ts | 1 + examples/sandbox/src/lib/map.ts | 4 +- examples/sandbox/src/lib/set.ts | 2 +- .../{schema-core => registry}/src/bounded.ts | 20 +- packages/registry/src/exports.ts | 81 +++---- packages/registry/src/globalThis.ts | 4 +- packages/registry/src/index.ts | 1 - packages/registry/src/pick.ts | 3 +- packages/registry/src/predicate.ts | 222 +++++++++++++++++- packages/registry/src/symbol.ts | 9 + packages/registry/src/types.ts | 2 +- packages/registry/src/uri.ts | 11 +- packages/registry/test/bounded.test.ts | 60 +++++ packages/registry/test/satisfies.test.ts | 2 +- packages/schema-core/package.json | 3 +- .../src/__generated__/__manifest__.ts | 2 +- packages/schema-core/src/combinators.ts | 5 +- packages/schema-core/src/core.ts | 42 ++-- packages/schema-core/src/exports.ts | 9 - packages/schema-core/src/extensions.ts | 42 ++-- packages/schema-core/src/has.ts | 4 +- packages/schema-core/src/key.ts | 8 +- packages/schema-core/src/namespace.ts | 60 ++--- packages/schema-core/src/predicates.ts | 5 +- packages/schema-core/src/readonlyArray.ts | 16 -- packages/schema-core/src/{ => schemas}/any.ts | 0 .../schema-core/src/{ => schemas}/array.ts | 37 ++- .../schema-core/src/{ => schemas}/bigint.ts | 13 +- .../schema-core/src/{ => schemas}/boolean.ts | 0 packages/schema-core/src/{ => schemas}/eq.ts | 6 +- .../schema-core/src/{ => schemas}/integer.ts | 6 +- .../src/{ => schemas}/intersect.ts | 16 +- .../schema-core/src/{ => schemas}/never.ts | 0 .../schema-core/src/{ => schemas}/null.ts | 0 .../schema-core/src/{ => schemas}/number.ts | 14 +- .../schema-core/src/{ => schemas}/object.ts | 15 +- packages/schema-core/src/{ => schemas}/of.ts | 8 +- .../schema-core/src/{ => schemas}/optional.ts | 19 +- .../schema-core/src/{ => schemas}/record.ts | 16 +- .../schema-core/src/{ => schemas}/string.ts | 15 +- .../schema-core/src/{ => schemas}/symbol.ts | 0 .../schema-core/src/{ => schemas}/tuple.ts | 16 +- .../src/{ => schemas}/undefined.ts | 0 .../schema-core/src/{ => schemas}/union.ts | 16 +- .../schema-core/src/{ => schemas}/unknown.ts | 0 .../schema-core/src/{ => schemas}/void.ts | 0 packages/schema-core/src/types.ts | 6 +- packages/schema-core/test/bounded.test.ts | 53 +---- packages/schema-core/test/schema.test.ts | 12 - packages/schema-generator/package.json | 2 +- .../src/__generated__/__manifest__.ts | 2 +- .../test/test-data/array/core.ts | 67 ++++-- .../test/test-data/bigint/core.ts | 9 +- .../test/test-data/eq/core.ts | 7 +- .../test/test-data/integer/core.ts | 7 +- .../test/test-data/intersect/core.ts | 30 ++- .../test/test-data/number/core.ts | 15 +- .../test/test-data/object/core.ts | 54 +++-- .../test/test-data/of/index.ts | 44 ++++ .../test/test-data/optional/core.ts | 34 ++- .../test/test-data/record/core.ts | 31 ++- .../test/test-data/string/core.ts | 14 +- .../test/test-data/tuple/core.ts | 67 +++--- .../test/test-data/union/core.ts | 30 ++- packages/schema/README.md | 38 +++ packages/schema/package.json | 58 +++++ .../schema/src/__generated__/__manifest__.ts | 58 +++++ packages/schema/src/build.ts | 109 +++++++++ packages/schema/src/exports.ts | 1 + packages/schema/src/index.ts | 1 + packages/schema/src/schemas/any.ts | 36 +++ packages/schema/src/schemas/array.ts | 128 ++++++++++ packages/schema/src/schemas/bigint.ts | 99 ++++++++ packages/schema/src/schemas/boolean.ts | 37 +++ packages/schema/src/schemas/eq.ts | 41 ++++ packages/schema/src/schemas/integer.ts | 100 ++++++++ packages/schema/src/schemas/intersect.ts | 50 ++++ packages/schema/src/schemas/never.ts | 37 +++ packages/schema/src/schemas/null.ts | 39 +++ packages/schema/src/schemas/number.ts | 139 +++++++++++ packages/schema/src/schemas/object.ts | 79 +++++++ packages/schema/src/schemas/of.ts | 44 ++++ packages/schema/src/schemas/optional.ts | 55 +++++ packages/schema/src/schemas/record.ts | 50 ++++ packages/schema/src/schemas/string.ts | 102 ++++++++ packages/schema/src/schemas/symbol.ts | 36 +++ packages/schema/src/schemas/tuple.ts | 86 +++++++ packages/schema/src/schemas/undefined.ts | 36 +++ packages/schema/src/schemas/union.ts | 50 ++++ packages/schema/src/schemas/unknown.ts | 36 +++ packages/schema/src/schemas/void.ts | 36 +++ packages/schema/src/version.ts | 3 + packages/schema/test/version.test.ts | 10 + packages/schema/tsconfig.build.json | 19 ++ packages/schema/tsconfig.json | 8 + packages/schema/tsconfig.src.json | 19 ++ packages/schema/tsconfig.test.json | 20 ++ packages/schema/vite.config.ts | 6 + pnpm-lock.yaml | 25 ++ symbol.object | 0 tsconfig.base.json | 42 +++- tsconfig.build.json | 3 +- tsconfig.json | 3 +- 104 files changed, 2554 insertions(+), 493 deletions(-) rename packages/{schema-core => registry}/src/bounded.ts (77%) create mode 100644 packages/registry/test/bounded.test.ts delete mode 100644 packages/schema-core/src/readonlyArray.ts rename packages/schema-core/src/{ => schemas}/any.ts (100%) rename packages/schema-core/src/{ => schemas}/array.ts (86%) rename packages/schema-core/src/{ => schemas}/bigint.ts (91%) rename packages/schema-core/src/{ => schemas}/boolean.ts (100%) rename packages/schema-core/src/{ => schemas}/eq.ts (81%) rename packages/schema-core/src/{ => schemas}/integer.ts (94%) rename packages/schema-core/src/{ => schemas}/intersect.ts (79%) rename packages/schema-core/src/{ => schemas}/never.ts (100%) rename packages/schema-core/src/{ => schemas}/null.ts (100%) rename packages/schema-core/src/{ => schemas}/number.ts (95%) rename packages/schema-core/src/{ => schemas}/object.ts (87%) rename packages/schema-core/src/{ => schemas}/of.ts (93%) rename packages/schema-core/src/{ => schemas}/optional.ts (78%) rename packages/schema-core/src/{ => schemas}/record.ts (75%) rename packages/schema-core/src/{ => schemas}/string.ts (91%) rename packages/schema-core/src/{ => schemas}/symbol.ts (100%) rename packages/schema-core/src/{ => schemas}/tuple.ts (91%) rename packages/schema-core/src/{ => schemas}/undefined.ts (100%) rename packages/schema-core/src/{ => schemas}/union.ts (81%) rename packages/schema-core/src/{ => schemas}/unknown.ts (100%) rename packages/schema-core/src/{ => schemas}/void.ts (100%) create mode 100644 packages/schema-generator/test/test-data/of/index.ts create mode 100644 packages/schema/README.md create mode 100644 packages/schema/package.json create mode 100644 packages/schema/src/__generated__/__manifest__.ts create mode 100755 packages/schema/src/build.ts create mode 100644 packages/schema/src/exports.ts create mode 100644 packages/schema/src/index.ts create mode 100644 packages/schema/src/schemas/any.ts create mode 100644 packages/schema/src/schemas/array.ts create mode 100644 packages/schema/src/schemas/bigint.ts create mode 100644 packages/schema/src/schemas/boolean.ts create mode 100644 packages/schema/src/schemas/eq.ts create mode 100644 packages/schema/src/schemas/integer.ts create mode 100644 packages/schema/src/schemas/intersect.ts create mode 100644 packages/schema/src/schemas/never.ts create mode 100644 packages/schema/src/schemas/null.ts create mode 100644 packages/schema/src/schemas/number.ts create mode 100644 packages/schema/src/schemas/object.ts create mode 100644 packages/schema/src/schemas/of.ts create mode 100644 packages/schema/src/schemas/optional.ts create mode 100644 packages/schema/src/schemas/record.ts create mode 100644 packages/schema/src/schemas/string.ts create mode 100644 packages/schema/src/schemas/symbol.ts create mode 100644 packages/schema/src/schemas/tuple.ts create mode 100644 packages/schema/src/schemas/undefined.ts create mode 100644 packages/schema/src/schemas/union.ts create mode 100644 packages/schema/src/schemas/unknown.ts create mode 100644 packages/schema/src/schemas/void.ts create mode 100644 packages/schema/src/version.ts create mode 100644 packages/schema/test/version.test.ts create mode 100644 packages/schema/tsconfig.build.json create mode 100644 packages/schema/tsconfig.json create mode 100644 packages/schema/tsconfig.src.json create mode 100644 packages/schema/tsconfig.test.json create mode 100644 packages/schema/vite.config.ts create mode 100644 symbol.object diff --git a/README.md b/README.md index 28c3f35d..35e8f03d 100644 --- a/README.md +++ b/README.md @@ -404,5 +404,12 @@ flowchart TD schema-valibot-adapter(schema-valibot-adapter) -.-> json(json) schema-valibot-adapter(schema-valibot-adapter) -.-> registry(registry) schema-zod-adapter(schema-zod-adapter) -.-> json(json) - schema-zod-adapter(schema-zod-adapter) -.depends on.-> registry(registry) + schema-zod-adapter(schema-zod-adapter) -.-> registry(registry) + schema(schema) -.-> derive-codec(derive-codec) + schema(schema) -.-> derive-equals(derive-equals) + schema(schema) -.-> registry(registry) + schema(schema) -.-> schema-core(schema-core) + schema(schema) -.-> schema-generator(schema-generator) + schema(schema) -.-> schema-to-json-schema(schema-to-json-schema) + schema(schema) -.depends on.-> schema-to-string(schema-to-string) ``` diff --git a/config/__generated__/package-list.ts b/config/__generated__/package-list.ts index 3a9d053b..160fc59c 100644 --- a/config/__generated__/package-list.ts +++ b/config/__generated__/package-list.ts @@ -4,6 +4,7 @@ export const PACKAGES = [ "packages/derive-validators", "packages/json", "packages/registry", + "packages/schema", "packages/schema-core", "packages/schema-generator", "packages/schema-seed", diff --git a/examples/sandbox/src/lib/map.ts b/examples/sandbox/src/lib/map.ts index de5981af..573bc9bc 100644 --- a/examples/sandbox/src/lib/map.ts +++ b/examples/sandbox/src/lib/map.ts @@ -53,8 +53,8 @@ export namespace map { export type type = never | Map export function def(k: K, v: V): map { type T = Map - let keyPredicate = T.isPredicate(k) ? k : (_?: any) => true - let valuePredicate = T.isPredicate(v) ? v : (_?: any) => true + let keyPredicate = T._isPredicate(k) ? k : (_?: any) => true + let valuePredicate = T._isPredicate(v) ? v : (_?: any) => true function MapSchema(u: map['_type'] | {} | null | undefined): u is T { if (!(u instanceof globalThis.Map)) return false else { diff --git a/examples/sandbox/src/lib/set.ts b/examples/sandbox/src/lib/set.ts index 84d85894..94aea2c0 100644 --- a/examples/sandbox/src/lib/set.ts +++ b/examples/sandbox/src/lib/set.ts @@ -40,7 +40,7 @@ export namespace set { export function def(x: S): set { type T = Set - const predicate = T.isPredicate(x) ? x : (_?: any) => true + const predicate = T._isPredicate(x) ? x : (_?: any) => true function SetSchema(u: unknown): u is T { if (!(u instanceof globalThis.Set)) return false else { diff --git a/packages/schema-core/src/bounded.ts b/packages/registry/src/bounded.ts similarity index 77% rename from packages/schema-core/src/bounded.ts rename to packages/registry/src/bounded.ts index 4ef69e98..d9f247b9 100644 --- a/packages/schema-core/src/bounded.ts +++ b/packages/registry/src/bounded.ts @@ -1,17 +1,7 @@ -import { fn } from '@traversable/registry' +import { isNullable, isNumeric } from './predicate.js' +import { assertIsNotCalled } from './function.js' -export interface Bounds { - gte?: T - lte?: T - gt?: T - lt?: T -} - -/** @internal */ -const isNumeric = (u: unknown) => typeof u === 'number' || typeof u === 'bigint' - -/** @internal */ -const isNullable = (u: unknown) => u == null +export interface Bounds { gte?: T, lte?: T, gt?: T, lt?: T } export function within({ gte = Number.MIN_SAFE_INTEGER, lte = Number.MAX_SAFE_INTEGER, gt, lt }: Bounds) { return (x: number): boolean => { @@ -19,7 +9,7 @@ export function within({ gte = Number.MIN_SAFE_INTEGER, lte = Number.MAX_SAFE_IN case isNullable(gt) && isNullable(lt): return gte <= x && x <= lte case isNumeric(gt): return isNumeric(lt) ? gt < x && x < lt : gt < x && x <= lte case isNumeric(lt): return gte <= x && x < lt - default: return fn.assertIsNotCalled(lt, gt) + default: return assertIsNotCalled(lt, gt) } } } @@ -32,7 +22,7 @@ export function withinBig({ gte = void 0, lte = void 0, gt, lt }: Bounds = import('./types.js').Equal +export type * from './satisfies.js' +export type * from './types.js' + +export * from './globalThis.js' +export * as fn from './function.js' +export * as Print from './print.js' +export * from './predicate.js' + +export { VERSION } from './version.js' + export type { GlobalOptions, OptionalTreatment, SchemaOptions } from './options.js' export type { GlobalConfig, SchemaConfig } from './config.js' export { applyOptions, configure, defaults, eqDefaults, getConfig } from './config.js' - +export type { TypeName } from './typeName.js' +export { typeName } from './typeName.js' +export type { Bounds } from './bounded.js' +export { carryover, within, withinBig } from './bounded.js' +export { join } from './join.js' +export { has } from './has.js' +export { parseArgs } from './parseArgs.js' +export { escape, isQuoted, isValidIdentifier, parseKey } from './parse.js' +export { IsStrictlyEqual, SameType, SameValue, SameValueNumber, deep as deepEquals, lax as laxEquals } from './equals.js' +export { unsafeCompact } from './compact.js' +export { bindUserExtensions } from './bindUserExtensions.js' +export { safeCoerce } from './safeCoerce.js' +export { map } from './mapObject.js' +export { objectFromKeys, omit, omit_, omitWhere, omitMethods, pick, pick_, pickWhere } from './pick.js' +export { merge, mut } from './merge.js' +export { ValueSet } from './set.js' export { /** @internal */ fromPath as __fromPath, @@ -51,26 +47,3 @@ export { /** @internal */ parsePath as __parsePath, } from './has.js' - -export { unsafeCompact } from './compact.js' -export { bindUserExtensions } from './bindUserExtensions.js' -export { safeCoerce } from './safeCoerce.js' - -export { map } from './mapObject.js' - -export { - objectFromKeys, - omit, - omit_, - omitWhere, - omitMethods, - pick, - pick_, - pickWhere, -} from './pick.js' - -export { merge, mut } from './merge.js' - -export { ValueSet } from './set.js' - -export { isPredicate } from './predicate.js' diff --git a/packages/registry/src/globalThis.ts b/packages/registry/src/globalThis.ts index 4631ebe2..1c124700 100644 --- a/packages/registry/src/globalThis.ts +++ b/packages/registry/src/globalThis.ts @@ -36,7 +36,9 @@ export const Object_values = globalThis.Object.values export const Object_hasOwn : (u: unknown, k: K) => u is Record - = (u, k): u is never => !!u && typeof u === 'object' && globalThis.Object.prototype.hasOwnProperty.call(u, k) + = (u, k): u is never => !!u + && (typeof u === 'object' || typeof u === 'function') + && globalThis.Object.prototype.hasOwnProperty.call(u, k) export const Object_keys : (x: T) => (K)[] diff --git a/packages/registry/src/index.ts b/packages/registry/src/index.ts index 704f5088..410a4bcb 100644 --- a/packages/registry/src/index.ts +++ b/packages/registry/src/index.ts @@ -1,2 +1 @@ export * from './exports.js' -export * as registry from './exports.js' diff --git a/packages/registry/src/pick.ts b/packages/registry/src/pick.ts index 0dc7422a..a6e68123 100644 --- a/packages/registry/src/pick.ts +++ b/packages/registry/src/pick.ts @@ -26,6 +26,7 @@ export declare namespace pick { type Where = never | { [K in keyof T as T[K] extends S | undefined ? K : never]: T[K] } } +export type Omit = keyof T extends K ? T : { [P in keyof T as P extends K ? never : P]: T[P] } export type omit = never | { [P in keyof T as P extends K ? never : P]: T[P] } export declare namespace omit { type Lax = never | { [P in keyof T as P extends K ? never : P]: T[P] } @@ -62,7 +63,7 @@ export function pick(x: { [x: keyof any]: unknown }, ks: (keyof any)[]) { } } -export function omit(x: T, ks: K[]): omit +export function omit(x: T, ks: K[]): Omit export function omit(x: { [x: keyof any]: unknown }, ks: (keyof any)[]) { if (!x || typeof x !== 'object') return x if (ks.length === 0) return x diff --git a/packages/registry/src/predicate.ts b/packages/registry/src/predicate.ts index 1c1f24f4..15c346dd 100644 --- a/packages/registry/src/predicate.ts +++ b/packages/registry/src/predicate.ts @@ -1,4 +1,220 @@ +import type { Intersect, Unknown } from './types.js' +import type { SchemaOptions } from './options.js' +import * as URI from './uri.js' +import * as symbol from './symbol.js' +import { + Array_isArray, + Number_isInteger, + Object_hasOwn, + Object_keys, + Object_values, +} from './globalThis.js' + +export function isNonNullable(x: {} | Unknown) { return x != null } + +/** @internal */ +function coerce(f: (x: S) => T): ((x: S) => T) +function coerce(f: (x: never) => unknown): {} { + return f === globalThis.Boolean ? isNonNullable : f +} + +/** @internal */ +let apply + : (x: S) => (f: (x: S) => T) => T + = (x) => ((f: Function) => { + if ((f) === globalThis.Boolean) return (x != null) + else return f(x) + }) + +/** @internal */ +let bind + : (f: (x: S) => T) => ((x: S) => T) + = (f) => { + let g = coerce(f) + return (x) => g(x) + } + +/** @internal */ +export let _isPredicate + : (x: unknown) => x is { (): boolean; (x: S): x is T } + = ((x: any) => typeof x === 'function') as never + +/** Nullary */ +export function isNever(_: unknown): _ is never { return false } +export function isAny(_: unknown): _ is any { return true } +export function isUnknown(_: unknown): _ is unknown { return true } +export function isVoid(x: void | Unknown): x is void { return x === void 0 } +export function isNull(x: null | Unknown) { return x === null } +export function isUndefined(x: undefined | Unknown) { return x === undefined } +export function isSymbol(x: symbol | Unknown) { return typeof x === 'symbol' } +export function isBoolean(x: boolean | Unknown) { return typeof x === 'boolean' } +export function isInteger(x: number | Unknown) { return Number_isInteger(x) } +export function isNumber(x: number | Unknown) { return typeof x === 'number' } +export function isBigInt(x: bigint | Unknown) { return typeof x === 'bigint' } +export function isString(x: string | Unknown) { return typeof x === 'string' } +export function isAnyArray(x: unknown[] | Unknown) { return Array_isArray(x) } +export function isAnyObject(x: { [x: string]: unknown } | Unknown): x is { [x: string]: unknown } { + return !!x && typeof x === 'object' && !Array_isArray(x) +} + +export function isFunction(x: Function | unknown) { return typeof x === 'function' } +export function isComposite(x: { [x: string]: T } | T[] | Unknown): x is { [x: string]: T } | T[] { return !!x && typeof x === 'object' } + +/** Unary+ */ +export let array + : (fn: (x: unknown) => x is T) => ((x: T[] | Unknown) => x is T[]) + = (fn) => function isArrayOf(x): x is never { return Array_isArray(x) && x.every(bind(fn)) } + +export let record + : (fn: (x: unknown) => x is T) => ((x: Record | Unknown) => x is Record) + = (fn) => function isRecordOf(x): x is never { return isAnyObject(x) && Object_values(x).every(bind(fn)) } + +export let union + : (fns: { [I in keyof T]: (x: unknown) => x is T[I] }) => ((x: T[number] | Unknown) => x is T[number]) + = (fns) => function isAnyOf(x): x is never { return fns.some(apply(x)) } + +export let intersect + : (fns: { [I in keyof T]: (x: unknown) => x is T[I] }) => ((x: Intersect | Unknown) => x is Intersect) + = (fns) => function isAllOf(x): x is never { return fns.every(apply(x)) } + +export let optional + : (fn: (x: unknown) => x is T) => (x: undefined | T | Unknown) => x is undefined | T + = (fn) => function isOptionally(u): u is never { return u === void 0 || coerce(fn)(u) } + +/** Composites */ +export function isNumeric(x: number | bigint | Unknown) { return typeof x === 'number' || typeof x === 'bigint' } +export function isNullable(x: null | undefined | Unknown) { return x == null } + + +type Target = S extends { (_: any): _ is infer T } ? T : S extends { (x: infer T): boolean } ? T : never +type Object$ = (x: unknown) => x is { [K in keyof T]: Target } + +function isOptionalSchema(x: unknown): x is ((x: unknown) => x is unknown) & { [symbol.tag]: URI.optional, def: (x: unknown) => x is unknown } { + return !!x && (typeof x === 'object' || typeof x === 'function') && 'tag' in x && x.tag === URI.optional && 'def' in x && typeof x.def === 'function' +} +function isRequiredSchema(x: unknown): x is (_: unknown) => _ is T { + return !!x && !isOptionalSchema(x) +} +function isOptionalNotUndefinedSchema(x: unknown): x is {} { + return !!x && isOptionalSchema(x) && x.def(undefined) === false +} +function isUndefinedSchema(x: unknown): x is { tag: URI.undefined } { + return !!x && typeof x === 'function' && 'tag' in x && x.tag === URI.undefined +} + +function hasOwn(x: unknown, key: K): x is { [P in K]: unknown } +function hasOwn(x: unknown, key: keyof any): x is { [x: string]: unknown } { + return typeof x === 'function' ? Object_hasOwn(x, key) + : typeof key === "symbol" + ? isComposite(x) && key in x + : Object_hasOwn(x, key) +} + +export function exactOptional( + fns: Record boolean>, + xs: Record +): boolean { + for (const k in fns) { + const fn = coerce(fns[k]) + switch (true) { + // case q === (globalThis.Boolean as never): { if (Object_hasOwn(x, k)) return x[k] != null; else return false } + case isUndefinedSchema(fn) && !Object_hasOwn(xs, k): return false + case isOptionalNotUndefinedSchema(fn) && Object_hasOwn(xs, k) && xs[k] === undefined: return false + case isOptionalSchema(fn) && !Object_hasOwn(xs, k): continue + case isRequiredSchema(fn) && !Object_hasOwn(xs, k): return false + case !fn(xs[k]): return false + default: continue + } + } + return true +} + +export function presentButUndefinedIsOK( + fns: Record boolean>, + x: Record +): boolean { + for (const k in fns) { + const fn = coerce(fns[k]) + switch (true) { + // case fn === (globalThis.Boolean as never): { if (hasOwn(x, k)) return x[k] != null; else return false } + case isOptionalSchema(fn) && !hasOwn(x, k): continue + case isOptionalSchema(fn) && hasOwn(x, k) && x[k] === undefined: continue + case isOptionalSchema(fn) && hasOwn(x, k) && fn(x[k]): continue + case isOptionalSchema(fn) && hasOwn(x, k) && !fn(x[k]): return false + case isRequiredSchema(fn) && !hasOwn(x, k): return false + case isRequiredSchema(fn) && hasOwn(x, k) && fn(x[k]) === true: continue + default: return false + } + } + return true +} + +export function treatUndefinedAndOptionalAsTheSame( + fns: Record boolean>, + x: Record +) { + const ks = Object_keys(fns) + for (const k of ks) { + const fn = coerce(fns[k]) + const y = x[k] + if (!fn(y)) return false + } + return true +} + +export function object( + fns: { [K in keyof T]: (x: unknown) => x is T[K] }, + config?: Required, +): (x: T | Unknown) => x is T { + return function isFiniteObject(x: unknown): x is never { + switch (true) { + case !x: return false + case !isAnyObject(x): return false + case !config?.treatArraysAsObjects && Array_isArray(x): return false + case config?.optionalTreatment === 'exactOptional': return exactOptional(fns, x) + case config?.optionalTreatment === 'presentButUndefinedIsOK': return presentButUndefinedIsOK(fns, x) + case config?.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame': return treatUndefinedAndOptionalAsTheSame(fns, x) + default: throw globalThis.Error( + + '(["@traversable/schema-core/predicates/object$"] \ + \ + Expected "optionalTreatment" to be one of: \ + \ + - "exactOptional" \ + - "presentButUndefinedIsOK" \ + - "treatUndefinedAndOptionalAsTheSame" \ + \ + Got: ' + globalThis.JSON.stringify(config?.optionalTreatment) + ) + } + } +} + +export type TupleOptions = { minLength?: number; } & SchemaOptions + +export let tuple + : < + T extends readonly unknown[], + Opts extends TupleOptions + >( + fns: { [I in keyof T]: (u: unknown) => u is T[I] }, + options: Opts + ) => ((u: unknown) => u is T) + + = (fns, options) => { + const checkLength = (xs: readonly unknown[]) => + options?.minLength === void 0 + ? (xs.length === fns.length) + : options.minLength === -1 + ? (xs.length === fns.length) + : (xs.length >= options.minLength && fns.length >= xs.length) + + function isFiniteArray(u: unknown): u is never { + return Array_isArray(u) + && checkLength(u) + && fns.every((fn, ix) => coerce(fn)(u[ix])) + } + + return isFiniteArray + } -export const isPredicate - : (src: unknown) => src is { (): boolean; (x: S): x is T } - = (src: unknown): src is never => typeof src === 'function' diff --git a/packages/registry/src/symbol.ts b/packages/registry/src/symbol.ts index 99600f5c..5e137c13 100644 --- a/packages/registry/src/symbol.ts +++ b/packages/registry/src/symbol.ts @@ -20,6 +20,7 @@ export { symbol_number as number, symbol_object as object, symbol_optional as optional, + symbol_order as order, symbol_top as top, symbol_record as record, symbol_string as string, @@ -33,6 +34,8 @@ export { symbol_union as union, symbol_void as void, symbol_symbol as symbol, + symbol_any_array as any_array, + symbol_any_object as any_object, } import * as URI from './uri.js' @@ -63,6 +66,7 @@ const symbol_record = Symbol.for(URI.record) const symbol_string = Symbol.for(URI.string) const symbol_symbol = Symbol.for(URI.symbol) const symbol_tag = Symbol.for(URI.tag) +const symbol_order = Symbol.for(URI.order) const symbol_tuple = Symbol.for(URI.tuple) const symbol_type = Symbol.for(URI.type) const symbol_type_error = Symbol.for(URI.type_error) @@ -71,8 +75,12 @@ const symbol_unknown = Symbol.for(URI.unknown) const symbol_undefined = Symbol.for(URI.undefined) const symbol_union = Symbol.for(URI.union) const symbol_void = Symbol.for(URI.void) +const symbol_any_array = Symbol.for(URI.any_array) +const symbol_any_object = Symbol.for(URI.any_object) type symbol_any = typeof symbol_any +type symbol_any_array = typeof symbol_any_array +type symbol_any_object = typeof symbol_any_object type symbol_array = typeof symbol_array type symbol_bad_data = typeof symbol_bad_data type symbol_bigint = typeof symbol_bigint @@ -92,6 +100,7 @@ type symbol_null = typeof symbol_null type symbol_number = typeof symbol_number type symbol_object = typeof symbol_object type symbol_optional = typeof symbol_optional +type symbol_order = typeof symbol_order type symbol_top = typeof symbol_top type symbol_record = typeof symbol_record type symbol_string = typeof symbol_string diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index 024b9805..ea842bd9 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -27,7 +27,7 @@ export type Integer> = [T] extends [number] // transforms export type Force = never | { -readonly [K in keyof T]: T[K] } -export type Intersect = X extends readonly [infer H, ...infer T] ? Intersect : _ +export type Intersect = T extends readonly [infer Head, ...infer Tail] ? Intersect : Out export type PickIfDefined< T, diff --git a/packages/registry/src/uri.ts b/packages/registry/src/uri.ts index a97afdb5..12147d99 100644 --- a/packages/registry/src/uri.ts +++ b/packages/registry/src/uri.ts @@ -23,6 +23,8 @@ export { URI_void as void, URI_enum as enum, // misc. + URI_any_array as any_array, + URI_any_object as any_object, URI_bad_data as bad_data, URI_bottom as bottom, URI_cache_hit as cache_hit, @@ -30,6 +32,7 @@ export { URI_has as has, URI_nonnullable as nonnullable, URI_notfound as notfound, + URI_order as order, URI_tag as tag, URI_top as top, URI_type as type, @@ -89,7 +92,11 @@ const URI_void = `${NS}void` as const type URI_void = typeof URI_void // misc. -const URI_bad_data = `${NS}bad_data` +const URI_any_array = `${NS}any_array` as const +type URI_any_array = typeof URI_any_array +const URI_any_object = `${NS}any_object` as const +type URI_any_object = typeof URI_any_object +const URI_bad_data = `${NS}bad_data` as const type URI_bad_data = typeof URI_bad_data const URI_bottom = `${NS}bottom` as const type URI_bottom = typeof URI_bottom @@ -105,6 +112,8 @@ const URI_notfound = `${NS}notfound` as const type URI_notfound = typeof URI_notfound const URI_tag = `${NS}tag` as const type URI_tag = typeof URI_tag +const URI_order = `${NS}order` as const +type URI_order = typeof URI_order const URI_top = `${NS}top` as const type URI_top = typeof URI_top const URI_type = `${NS}type` as const diff --git a/packages/registry/test/bounded.test.ts b/packages/registry/test/bounded.test.ts new file mode 100644 index 00000000..7099fccf --- /dev/null +++ b/packages/registry/test/bounded.test.ts @@ -0,0 +1,60 @@ +import * as vi from 'vitest' +import { carryover, within, withinBig } from '@traversable/registry' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core/bounded❳', () => { + vi.it('〖⛳️〗‹ ❲within❳', () => { + // SUCCESS + vi.assert.isTrue(within({ gt: 0 })(1)) + vi.assert.isTrue(within({ gt: 0, lt: 2 })(1)) + vi.assert.isTrue(within({ lt: 2 })(1)) + // FAILURE + vi.assert.isFalse(within({ gt: 0 })(0)) + vi.assert.isFalse(within({ gt: 0, lt: 0 })(0)) + vi.assert.isFalse(within({ lt: 0 })(0)) + /* @ts-expect-error */ + vi.assert.throws(() => within({ gt: '' })(0.5)) + }) + + vi.it('〖⛳️〗‹ ❲withinBig❳', () => { + // SUCCESS + vi.assert.isTrue(withinBig({})(1)) + vi.assert.isTrue(withinBig({ lte: 1 })(1)) + vi.assert.isTrue(withinBig({ gte: 1 })(1)) + vi.assert.isTrue(withinBig({ lt: 2 })(1)) + vi.assert.isTrue(withinBig({ gt: 0 })(1)) + vi.assert.isTrue(withinBig({ gt: 0, lt: 2 })(1)) + vi.assert.isTrue(withinBig({ gt: 0, lte: 1 })(1)) + vi.assert.isTrue(withinBig({ gte: 0, lt: 2 })(1)) + vi.assert.isTrue(withinBig({ gte: 0, lte: 1 })(1)) + vi.assert.isTrue(withinBig({ gt: 0, lt: 2, gte: 1 })(1)) + vi.assert.isTrue(withinBig({ gt: 0, lte: 1, gte: 1 })(1)) + vi.assert.isTrue(withinBig({ gte: 1, lt: 2, lte: 1 })(1)) + vi.assert.isTrue(withinBig({ gte: 1, lte: 1, lt: 2 })(1)) + vi.assert.isTrue(withinBig({ gte: 1, lte: 1, gt: 0 })(1)) + vi.assert.isTrue(withinBig({ gte: 1, lt: 2, gt: 0 })(1)) + vi.assert.isTrue(withinBig({ gte: 1, lte: 1, lt: 2, gt: 0 })(1)) + // FAILURE + vi.assert.isFalse(withinBig({ lte: 0 })(1)) + vi.assert.isFalse(withinBig({ gte: 1 })(-1)) + vi.assert.isFalse(withinBig({ lt: 0 })(1)) + vi.assert.isFalse(withinBig({ gt: 2 })(-1)) + vi.assert.isFalse(withinBig({ gt: 0, lt: 2 })(-1)) + vi.assert.isFalse(withinBig({ gt: 0, lte: 1 })(-1)) + vi.assert.isFalse(withinBig({ gte: 0, lt: 2 })(-1)) + vi.assert.isFalse(withinBig({ gte: 0, lte: 1 })(-1)) + vi.assert.isFalse(withinBig({ gt: 0, lt: 2, gte: 1 })(-1)) + vi.assert.isFalse(withinBig({ gt: 0, lte: 1, gte: 1 })(-1)) + vi.assert.isFalse(withinBig({ gte: 1, lt: 2, lte: 1 })(-1)) + vi.assert.isFalse(withinBig({ gte: 1, lte: 1, lt: 2 })(-1)) + vi.assert.isFalse(withinBig({ gte: 1, lte: 1, gt: 0 })(-1)) + vi.assert.isFalse(withinBig({ gte: 1, lt: 2, gt: 0 })(-1)) + vi.assert.isFalse(withinBig({ gte: 1, lte: 1, lt: 2, gt: 0 })(-1)) + /* @ts-expect-error */ + vi.assert.throws(() => withinBig({ gt: '' })(0.5)) + }) + + vi.it('〖⛳️〗› ❲~carryover❳', () => { + vi.assert.deepEqual(carryover({}), {}) + }) +}) + diff --git a/packages/registry/test/satisfies.test.ts b/packages/registry/test/satisfies.test.ts index df6ba660..f82a0f00 100644 --- a/packages/registry/test/satisfies.test.ts +++ b/packages/registry/test/satisfies.test.ts @@ -1,5 +1,5 @@ import type { NonUnion } from '@traversable/registry' -import { Match } from '@traversable/registry' +import { Match } from '@traversable/registry/satisfies' import * as vi from 'vitest' vi.describe('〖⛳️〗‹‹‹ ❲@traversable/registry❳', () => { diff --git a/packages/schema-core/package.json b/packages/schema-core/package.json index 072c8663..e08f8a6b 100644 --- a/packages/schema-core/package.json +++ b/packages/schema-core/package.json @@ -17,7 +17,8 @@ "@traversable": { "generateExports": { "include": [ - "**/*.ts" + "**/*.ts", + "schemas/*.ts" ] }, "generateIndex": { diff --git a/packages/schema-core/src/__generated__/__manifest__.ts b/packages/schema-core/src/__generated__/__manifest__.ts index d6977800..d4747eee 100644 --- a/packages/schema-core/src/__generated__/__manifest__.ts +++ b/packages/schema-core/src/__generated__/__manifest__.ts @@ -16,7 +16,7 @@ export default { }, "@traversable": { "generateExports": { - "include": ["**/*.ts"] + "include": ["**/*.ts", "schemas/*.ts"] }, "generateIndex": { "include": ["**/*.ts"] diff --git a/packages/schema-core/src/combinators.ts b/packages/schema-core/src/combinators.ts index a86075f8..89f48abc 100644 --- a/packages/schema-core/src/combinators.ts +++ b/packages/schema-core/src/combinators.ts @@ -1,7 +1,6 @@ import type * as T from './types.js' -import { LowerBound, Schema, SchemaLike } from './types.js' -import type { of } from './of.js' - +import type { LowerBound } from './types.js' +import type { of } from './schemas/of.js' /** * ## {@link filter `t.filter`} diff --git a/packages/schema-core/src/core.ts b/packages/schema-core/src/core.ts index 88f31e6b..63a22d1b 100644 --- a/packages/schema-core/src/core.ts +++ b/packages/schema-core/src/core.ts @@ -3,27 +3,27 @@ import { fn, has, Object_assign, symbol, URI } from '@traversable/registry' import type { Guarded, Schema } from './types.js' -import type { of } from './of.js' -import { never as never_ } from './never.js' -import { any as any_ } from './any.js' -import { unknown as unknown_ } from './unknown.js' -import { void as void_ } from './void.js' -import { null as null_ } from './null.js' -import { undefined as undefined_ } from './undefined.js' -import { symbol as symbol_ } from './symbol.js' -import { boolean as boolean_ } from './boolean.js' -import { integer as integer } from './integer.js' -import { bigint as bigint_ } from './bigint.js' -import { number as number_ } from './number.js' -import { string as string_ } from './string.js' -import { eq } from './eq.js' -import { optional } from './optional.js' -import { array } from './array.js' -import { record } from './record.js' -import { union } from './union.js' -import { intersect } from './intersect.js' -import { tuple } from './tuple.js' -import { object as object_ } from './object.js' +import type { of } from './schemas/of.js' +import { never as never_ } from './schemas/never.js' +import { any as any_ } from './schemas/any.js' +import { unknown as unknown_ } from './schemas/unknown.js' +import { void as void_ } from './schemas/void.js' +import { null as null_ } from './schemas/null.js' +import { undefined as undefined_ } from './schemas/undefined.js' +import { symbol as symbol_ } from './schemas/symbol.js' +import { boolean as boolean_ } from './schemas/boolean.js' +import { integer as integer } from './schemas/integer.js' +import { bigint as bigint_ } from './schemas/bigint.js' +import { number as number_ } from './schemas/number.js' +import { string as string_ } from './schemas/string.js' +import { eq } from './schemas/eq.js' +import { optional } from './schemas/optional.js' +import { array } from './schemas/array.js' +import { record } from './schemas/record.js' +import { union } from './schemas/union.js' +import { intersect } from './schemas/intersect.js' +import { tuple } from './schemas/tuple.js' +import { object as object_ } from './schemas/object.js' export type Inline = never | of> diff --git a/packages/schema-core/src/exports.ts b/packages/schema-core/src/exports.ts index 70d4f7a1..14d18e7a 100644 --- a/packages/schema-core/src/exports.ts +++ b/packages/schema-core/src/exports.ts @@ -64,7 +64,6 @@ export type Predicate = [T] extends [never] export { clone } from './clone.js' -export type { Bounds } from './bounded.js' export type { FirstOptionalItem, Guard, @@ -79,14 +78,6 @@ export { get, get$ } from './utils.js' export { VERSION } from './version.js' -export { - /** @internal */ - within as __within, - /** @internal */ - withinBig as __withinBig, - /** @internal */ - carryover as __carryover, -} from './bounded.js' export { /** @internal */ trim as __trim, diff --git a/packages/schema-core/src/extensions.ts b/packages/schema-core/src/extensions.ts index c61c6ae7..cd37b25d 100644 --- a/packages/schema-core/src/extensions.ts +++ b/packages/schema-core/src/extensions.ts @@ -6,24 +6,24 @@ export { enum as t_enum, } from './enum.js' -export type { never as t_never } from './never.js' -export type { any as t_any } from './any.js' -export type { unknown as t_unknown } from './unknown.js' -export type { void as t_void } from './void.js' -export type { null as t_null } from './null.js' -export type { undefined as t_undefined } from './undefined.js' -export type { symbol as t_symbol } from './symbol.js' -export type { boolean as t_boolean } from './boolean.js' -export type { integer as t_integer } from './integer.js' -export type { bigint as t_bigint } from './bigint.js' -export type { number as t_number } from './number.js' -export type { string as t_string } from './string.js' -export type { eq as t_eq } from './eq.js' -export type { optional as t_optional } from './optional.js' -export type { array as t_array } from './array.js' -export type { record as t_record } from './record.js' -export type { union as t_union } from './union.js' -export type { intersect as t_intersect } from './intersect.js' -export type { tuple as t_tuple } from './tuple.js' -export type { object as t_object } from './object.js' -export type { of as t_of } from './of.js' +export type { never as t_never } from './schemas/never.js' +export type { any as t_any } from './schemas/any.js' +export type { unknown as t_unknown } from './schemas/unknown.js' +export type { void as t_void } from './schemas/void.js' +export type { null as t_null } from './schemas/null.js' +export type { undefined as t_undefined } from './schemas/undefined.js' +export type { symbol as t_symbol } from './schemas/symbol.js' +export type { boolean as t_boolean } from './schemas/boolean.js' +export type { integer as t_integer } from './schemas/integer.js' +export type { bigint as t_bigint } from './schemas/bigint.js' +export type { number as t_number } from './schemas/number.js' +export type { string as t_string } from './schemas/string.js' +export type { eq as t_eq } from './schemas/eq.js' +export type { optional as t_optional } from './schemas/optional.js' +export type { array as t_array } from './schemas/array.js' +export type { record as t_record } from './schemas/record.js' +export type { union as t_union } from './schemas/union.js' +export type { intersect as t_intersect } from './schemas/intersect.js' +export type { tuple as t_tuple } from './schemas/tuple.js' +export type { object as t_object } from './schemas/object.js' +export type { of as t_of } from './schemas/of.js' diff --git a/packages/schema-core/src/has.ts b/packages/schema-core/src/has.ts index 2560e2bc..a47b9dc3 100644 --- a/packages/schema-core/src/has.ts +++ b/packages/schema-core/src/has.ts @@ -1,7 +1,7 @@ import { has as has_, URI } from '@traversable/registry' import type { Schema, SchemaLike } from './types.js' -import type { of } from './of.js' -import type * as t from './unknown.js' +import type { of } from './schemas/of.js' +import type * as t from './schemas/unknown.js' export interface has { tag: URI.has diff --git a/packages/schema-core/src/key.ts b/packages/schema-core/src/key.ts index d06b365b..2dbace05 100644 --- a/packages/schema-core/src/key.ts +++ b/packages/schema-core/src/key.ts @@ -1,6 +1,6 @@ -import { number } from './number.js' -import { string } from './string.js' -import { symbol } from './symbol.js' -import { union } from './union.js' +import { number } from './schemas/number.js' +import { string } from './schemas/string.js' +import { symbol } from './schemas/symbol.js' +import { union } from './schemas/union.js' export const key = union(string, number, symbol) diff --git a/packages/schema-core/src/namespace.ts b/packages/schema-core/src/namespace.ts index 01999697..248464bc 100644 --- a/packages/schema-core/src/namespace.ts +++ b/packages/schema-core/src/namespace.ts @@ -1,5 +1,28 @@ export * as recurse from './recursive.js' +export { never } from './schemas/never.js' +export { any } from './schemas/any.js' +export { unknown } from './schemas/unknown.js' +export { void } from './schemas/void.js' +export { null } from './schemas/null.js' +export { undefined } from './schemas/undefined.js' +export { symbol } from './schemas/symbol.js' +export { boolean } from './schemas/boolean.js' +export { integer } from './schemas/integer.js' +export { bigint } from './schemas/bigint.js' +export { number } from './schemas/number.js' +export { string } from './schemas/string.js' +export { eq } from './schemas/eq.js' +export { optional } from './schemas/optional.js' +export { array, readonlyArray } from './schemas/array.js' +export { record } from './schemas/record.js' +export { union } from './schemas/union.js' +export { intersect } from './schemas/intersect.js' +export { tuple } from './schemas/tuple.js' +export { object } from './schemas/object.js' +export { nonnullable } from './nonnullable.js' +export { of } from './schemas/of.js' + export type { Boundable, F, @@ -11,30 +34,6 @@ export type { typeOf as typeof, } from './core.ts' -export { never } from './never.js' -export { any } from './any.js' -export { unknown } from './unknown.js' -export { void } from './void.js' -export { null } from './null.js' -export { undefined } from './undefined.js' -export { symbol } from './symbol.js' -export { boolean } from './boolean.js' -export { integer } from './integer.js' -export { bigint } from './bigint.js' -export { number } from './number.js' -export { string } from './string.js' -export { eq } from './eq.js' -export { optional } from './optional.js' -export { array } from './array.js' -export { record } from './record.js' -export { union } from './union.js' -export { intersect } from './intersect.js' -export { tuple } from './tuple.js' -export { object } from './object.js' -export { nonnullable } from './nonnullable.js' -export { of } from './of.js' -export { readonlyArray } from './readonlyArray.js' - export { isLeaf, isNullary, @@ -56,14 +55,21 @@ export type { invalid, top, Entry, + FirstOptionalItem, + Guard, + Guarded, + IntersectType, + TupleType, LowerBound, Optional, Predicate, Required, Schema, + SchemaLike, Typeguard, UnknownSchema, Unspecified, + ValidateTuple, } from './types.js' export { has } from './has.js' @@ -76,6 +82,6 @@ export { enum } from './enum.js' /** * exported as escape hatches, to prevent collisions with built-in keywords */ -export { null as null_ } from './null.js' -export { undefined as undefined_ } from './undefined.js' -export { void as void_ } from './void.js' +export { null as null_ } from './schemas/null.js' +export { undefined as undefined_ } from './schemas/undefined.js' +export { void as void_ } from './schemas/void.js' diff --git a/packages/schema-core/src/predicates.ts b/packages/schema-core/src/predicates.ts index 4d038196..32223c6f 100644 --- a/packages/schema-core/src/predicates.ts +++ b/packages/schema-core/src/predicates.ts @@ -2,8 +2,8 @@ import type { Intersect, SchemaOptions } from '@traversable/registry' import { symbol as Symbol, URI } from '@traversable/registry' import type { Predicate } from './types.js' -import type { optional } from './optional.js' -import type * as t from './undefined.js' +import type { optional } from './schemas/optional.js' +import type * as t from './schemas/undefined.js' export { null_ as null, @@ -232,7 +232,6 @@ function treatUndefinedAndOptionalAsTheSame return true } - type Target = S extends { (_: any): _ is infer T } ? T : S extends { (u: infer T): boolean } ? T : never type Object$ = (u: unknown) => u is { [K in keyof T]: Target } diff --git a/packages/schema-core/src/readonlyArray.ts b/packages/schema-core/src/readonlyArray.ts deleted file mode 100644 index d1c7102b..00000000 --- a/packages/schema-core/src/readonlyArray.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { URI } from '@traversable/registry' - -import type { Schema, SchemaLike } from './types.js' -import { array } from './array.js' -import { Inline } from './core.js' - -export const readonlyArray: { - (schema: S, readonly: 'readonly'): readonlyArray - (schema: S): readonlyArray> -} = array -export interface readonlyArray { - (u: unknown): u is this['_type'] - tag: URI.array - def: S - _type: ReadonlyArray -} diff --git a/packages/schema-core/src/any.ts b/packages/schema-core/src/schemas/any.ts similarity index 100% rename from packages/schema-core/src/any.ts rename to packages/schema-core/src/schemas/any.ts diff --git a/packages/schema-core/src/array.ts b/packages/schema-core/src/schemas/array.ts similarity index 86% rename from packages/schema-core/src/array.ts rename to packages/schema-core/src/schemas/array.ts index 99b7e174..65c8c03c 100644 --- a/packages/schema-core/src/array.ts +++ b/packages/schema-core/src/schemas/array.ts @@ -1,22 +1,26 @@ -import type { Integer, Unknown } from '@traversable/registry' +import type { + Bounds, + Integer, + Unknown, +} from '@traversable/registry' import { Array_isArray, + array as arrayOf, bindUserExtensions, - isPredicate, + carryover, + within, + _isPredicate, has, Math_max, Math_min, Number_isSafeInteger, Object_assign, - safeCoerce, URI, } from '@traversable/registry' -import type { Guarded, Schema, SchemaLike } from './types.js' +import type { Guarded, Schema, SchemaLike } from '../namespace.js' + import type { of } from './of.js' -import type { Bounds } from './bounded.js' -import type { readonlyArray } from './readonlyArray.js' -import { carryover, within } from './bounded.js' /** @internal */ function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array @@ -50,8 +54,8 @@ export namespace array { let userExtensions: Record = { //<%= Extensions %> } - const arrayPredicate = isPredicate(x) ? array$(safeCoerce(x)) : Array_isArray - function ArraySchema(src: unknown) { return arrayPredicate(src) } + const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray + function ArraySchema(src: unknown) { return predicate(src) } ArraySchema.tag = URI.array ArraySchema.def = x ArraySchema.min = function arrayMin(minLength: Min) { @@ -84,10 +88,6 @@ export namespace array { } } -let array$ - : (f: (u: unknown) => u is unknown) => (u: unknown) => u is unknown[] - = (f: (u: unknown) => u is unknown) => (u: unknown): u is unknown[] => Array_isArray(u) && u.every(f) - export declare namespace array { interface core { (u: this['_type'] | Unknown): u is this['_type'] @@ -115,3 +115,14 @@ export declare namespace array { interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } type type = never | T } + +export const readonlyArray: { + (schema: S): readonlyArray + (schema: S): readonlyArray> +} = array +export interface readonlyArray { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: ReadonlyArray +} diff --git a/packages/schema-core/src/bigint.ts b/packages/schema-core/src/schemas/bigint.ts similarity index 91% rename from packages/schema-core/src/bigint.ts rename to packages/schema-core/src/schemas/bigint.ts index cd57891d..f7d55261 100644 --- a/packages/schema-core/src/bigint.ts +++ b/packages/schema-core/src/schemas/bigint.ts @@ -1,8 +1,11 @@ -import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, Object_assign, URI } from '@traversable/registry' - -import type { Bounds } from './bounded.js' -import { carryover, withinBig as within } from './bounded.js' +import type { Bounds, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Object_assign, + URI, + withinBig as within, +} from '@traversable/registry' export { bigint_ as bigint } diff --git a/packages/schema-core/src/boolean.ts b/packages/schema-core/src/schemas/boolean.ts similarity index 100% rename from packages/schema-core/src/boolean.ts rename to packages/schema-core/src/schemas/boolean.ts diff --git a/packages/schema-core/src/eq.ts b/packages/schema-core/src/schemas/eq.ts similarity index 81% rename from packages/schema-core/src/eq.ts rename to packages/schema-core/src/schemas/eq.ts index d1c14d26..d701a0f6 100644 --- a/packages/schema-core/src/eq.ts +++ b/packages/schema-core/src/schemas/eq.ts @@ -1,5 +1,5 @@ import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' -import { applyOptions, bindUserExtensions, isPredicate, Object_assign, URI } from '@traversable/registry' +import { applyOptions, bindUserExtensions, _isPredicate, Object_assign, URI } from '@traversable/registry' export function eq>(value: V, options?: Options): eq> export function eq(value: V, options?: Options): eq @@ -22,8 +22,8 @@ export namespace eq { //<%= Extensions %> } const options = applyOptions($) - const eqGuard = isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) - function EqSchema(src: unknown) { return eqGuard(src) } + const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return predicate(src) } EqSchema.tag = URI.eq EqSchema.def = x Object_assign(EqSchema, eq.userDefinitions) diff --git a/packages/schema-core/src/integer.ts b/packages/schema-core/src/schemas/integer.ts similarity index 94% rename from packages/schema-core/src/integer.ts rename to packages/schema-core/src/schemas/integer.ts index b3413072..2969e5a1 100644 --- a/packages/schema-core/src/integer.ts +++ b/packages/schema-core/src/schemas/integer.ts @@ -1,15 +1,15 @@ -import type { Integer, Unknown } from '@traversable/registry' +import type { Bounds, Integer, Unknown } from '@traversable/registry' import { bindUserExtensions, + carryover, Math_min, Math_max, Number_isSafeInteger, Object_assign, URI, + within, } from '@traversable/registry' -import type { Bounds } from './bounded.js' -import { carryover, within } from './bounded.js' export { integer } diff --git a/packages/schema-core/src/intersect.ts b/packages/schema-core/src/schemas/intersect.ts similarity index 79% rename from packages/schema-core/src/intersect.ts rename to packages/schema-core/src/schemas/intersect.ts index c9bee889..a3f89c92 100644 --- a/packages/schema-core/src/intersect.ts +++ b/packages/schema-core/src/schemas/intersect.ts @@ -1,8 +1,14 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + intersect as intersect$, + isUnknown as isAny, + Object_assign, + URI, +} from '@traversable/registry' -import type { Entry, IntersectType, Schema, SchemaLike } from './types.js' -import { intersect as intersect$ } from './predicates.js' +import type { Entry, IntersectType, Schema, SchemaLike } from '../namespace.js' export function intersect(...schemas: S): intersect export function intersect }>(...schemas: S): intersect @@ -24,8 +30,8 @@ export namespace intersect { let userExtensions: Record = { //<%= Extensions %> } - const allOf = xs.every(isPredicate) ? intersect$(xs.map(safeCoerce)) : (_?: unknown): _ is unknown => true - function IntersectSchema(src: unknown) { return allOf(src) } + const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny + function IntersectSchema(src: unknown) { return predicate(src) } IntersectSchema.tag = URI.intersect IntersectSchema.def = xs Object_assign(IntersectSchema, intersect.userDefinitions) diff --git a/packages/schema-core/src/never.ts b/packages/schema-core/src/schemas/never.ts similarity index 100% rename from packages/schema-core/src/never.ts rename to packages/schema-core/src/schemas/never.ts diff --git a/packages/schema-core/src/null.ts b/packages/schema-core/src/schemas/null.ts similarity index 100% rename from packages/schema-core/src/null.ts rename to packages/schema-core/src/schemas/null.ts diff --git a/packages/schema-core/src/number.ts b/packages/schema-core/src/schemas/number.ts similarity index 95% rename from packages/schema-core/src/number.ts rename to packages/schema-core/src/schemas/number.ts index 5173ea84..5972ae85 100644 --- a/packages/schema-core/src/number.ts +++ b/packages/schema-core/src/schemas/number.ts @@ -1,8 +1,14 @@ -import type { Unknown } from '@traversable/registry' -import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' +import type { Bounds, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' -import type { Bounds } from './bounded.js' -import { carryover, within } from './bounded.js' export { number_ as number } diff --git a/packages/schema-core/src/object.ts b/packages/schema-core/src/schemas/object.ts similarity index 87% rename from packages/schema-core/src/object.ts rename to packages/schema-core/src/schemas/object.ts index fa7a98c9..0b50af73 100644 --- a/packages/schema-core/src/object.ts +++ b/packages/schema-core/src/schemas/object.ts @@ -4,17 +4,17 @@ import { Array_isArray, bindUserExtensions, has, - isPredicate, - map, + _isPredicate, Object_assign, Object_keys, - safeCoerce, + record as record$, + object as object$, + isAnyObject, symbol, URI, } from '@traversable/registry' -import type { Entry, Optional, Required, Schema, SchemaLike } from './types.js' -import { record$, object as anyObject, object$ } from './predicates.js' +import type { Entry, Optional, Required, Schema, SchemaLike } from '../namespace.js' export { object_ as object } @@ -47,9 +47,8 @@ namespace object_ { const keys = Object_keys(xs) const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) const req = keys.filter((k) => !has(symbol.optional)(xs[k])) - const objectGuard = !record$(isPredicate)(xs) ? anyObject - : object$(map(xs, safeCoerce), applyOptions($)) - function ObjectSchema(src: unknown) { return objectGuard(src) } + const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) + function ObjectSchema(src: unknown) { return predicate(src) } ObjectSchema.tag = URI.object ObjectSchema.def = xs ObjectSchema.opt = opt diff --git a/packages/schema-core/src/of.ts b/packages/schema-core/src/schemas/of.ts similarity index 93% rename from packages/schema-core/src/of.ts rename to packages/schema-core/src/schemas/of.ts index 23886d7e..3ee3933b 100644 --- a/packages/schema-core/src/of.ts +++ b/packages/schema-core/src/schemas/of.ts @@ -2,11 +2,11 @@ import type { Unknown } from '@traversable/registry' import { Object_assign, URI } from '@traversable/registry' import type { - Guarded, Entry, - SchemaLike, Guard, -} from './types.js' + Guarded, + SchemaLike, +} from '../namespace.js' export interface of extends of.core { //<%= Types %> @@ -16,7 +16,7 @@ export function of(typeguard: S): Entry export function of(typeguard: S): of export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { typeguard.def = typeguard - return globalThis.Object.assign(typeguard, of.prototype) + return Object_assign(typeguard, of.prototype) } export namespace of { diff --git a/packages/schema-core/src/optional.ts b/packages/schema-core/src/schemas/optional.ts similarity index 78% rename from packages/schema-core/src/optional.ts rename to packages/schema-core/src/schemas/optional.ts index 6a8f1e5a..0574d117 100644 --- a/packages/schema-core/src/optional.ts +++ b/packages/schema-core/src/schemas/optional.ts @@ -1,8 +1,16 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, has, isPredicate, Object_assign, safeCoerce, symbol, URI } from '@traversable/registry' +import { + bindUserExtensions, + has, + _isPredicate, + optional as optional$, + Object_assign, + symbol, + URI, + isUnknown as isAny, +} from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from './types.js' -import { optional$ } from './predicates.js' +import type { Entry, Schema, SchemaLike } from '../namespace.js' export function optional(schema: S): optional export function optional(schema: S): optional> @@ -21,8 +29,8 @@ export namespace optional { let userExtensions: Record = { //<%= Extensions %> } - const optionalGuard = isPredicate(x) ? optional$(safeCoerce(x)) : (_: unknown) => true - function OptionalSchema(src: unknown) { return optionalGuard(src) } + const predicate = _isPredicate(x) ? optional$(x) : isAny + function OptionalSchema(src: unknown) { return predicate(src) } OptionalSchema.tag = URI.optional OptionalSchema.def = x OptionalSchema[symbol.optional] = 1 @@ -45,4 +53,3 @@ export declare namespace optional { } export type type = never | T } - diff --git a/packages/schema-core/src/record.ts b/packages/schema-core/src/schemas/record.ts similarity index 75% rename from packages/schema-core/src/record.ts rename to packages/schema-core/src/schemas/record.ts index 0da5c331..c4e54a9a 100644 --- a/packages/schema-core/src/record.ts +++ b/packages/schema-core/src/schemas/record.ts @@ -1,8 +1,14 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' +import { + isAnyObject, + record as record$, + bindUserExtensions, + _isPredicate, + Object_assign, + URI, +} from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from './types.js' -import { object as anyObject, record$ } from './predicates.js' +import type { Entry, Schema, SchemaLike } from '../namespace.js' export function record(schema: S): record export function record(schema: S): record> @@ -24,8 +30,8 @@ export namespace record { let userExtensions: Record = { //<%= Extensions %> } - const recordGuard = isPredicate(x) ? record$(safeCoerce(x)) : anyObject - function RecordSchema(src: unknown) { return recordGuard(src) } + const predicate = _isPredicate(x) ? record$(x) : isAnyObject + function RecordSchema(src: unknown) { return predicate(src) } RecordSchema.tag = URI.record RecordSchema.def = x Object_assign(RecordSchema, record.userDefinitions) diff --git a/packages/schema-core/src/string.ts b/packages/schema-core/src/schemas/string.ts similarity index 91% rename from packages/schema-core/src/string.ts rename to packages/schema-core/src/schemas/string.ts index e19bcae2..4276ca17 100644 --- a/packages/schema-core/src/string.ts +++ b/packages/schema-core/src/schemas/string.ts @@ -1,8 +1,13 @@ -import type { Integer, Unknown } from '@traversable/registry' -import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' - -import type { Bounds } from './bounded.js' -import { carryover, within } from './bounded.js' +import type { Bounds, Integer, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' export { string_ as string } diff --git a/packages/schema-core/src/symbol.ts b/packages/schema-core/src/schemas/symbol.ts similarity index 100% rename from packages/schema-core/src/symbol.ts rename to packages/schema-core/src/schemas/symbol.ts diff --git a/packages/schema-core/src/tuple.ts b/packages/schema-core/src/schemas/tuple.ts similarity index 91% rename from packages/schema-core/src/tuple.ts rename to packages/schema-core/src/schemas/tuple.ts index 672ac207..f2b9f6bb 100644 --- a/packages/schema-core/src/tuple.ts +++ b/packages/schema-core/src/schemas/tuple.ts @@ -9,11 +9,11 @@ import { bindUserExtensions, getConfig, has, - isPredicate, + _isPredicate, Object_assign, parseArgs, - safeCoerce, symbol, + tuple as tuple$, URI, } from '@traversable/registry' @@ -25,9 +25,9 @@ import type { SchemaLike, TupleType, ValidateTuple -} from './types.js' +} from '../namespace.js' + import type { optional } from './optional.js' -import { tuple$ } from './predicates.js' export { tuple } @@ -35,10 +35,8 @@ function tuple(...schemas: tuple.validate): tuple, S>> function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> - function tuple }>(...schemas: tuple.validate): tuple, T>> function tuple(...schemas: tuple.validate): tuple, S>> - function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { return tuple.def(...parseArgs(getConfig().schema, args)) } @@ -61,10 +59,8 @@ namespace tuple { const options = { ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) } satisfies tuple.InternalOptions - const tupleGuard = !xs.every(isPredicate) - ? Array_isArray - : tuple$(options)(xs.map(safeCoerce)) - function TupleSchema(src: unknown) { return tupleGuard(src) } + const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) + function TupleSchema(src: unknown) { return predicate(src) } TupleSchema.tag = URI.tuple TupleSchema.def = xs TupleSchema.opt = opt diff --git a/packages/schema-core/src/undefined.ts b/packages/schema-core/src/schemas/undefined.ts similarity index 100% rename from packages/schema-core/src/undefined.ts rename to packages/schema-core/src/schemas/undefined.ts diff --git a/packages/schema-core/src/union.ts b/packages/schema-core/src/schemas/union.ts similarity index 81% rename from packages/schema-core/src/union.ts rename to packages/schema-core/src/schemas/union.ts index e0587203..1e3705ee 100644 --- a/packages/schema-core/src/union.ts +++ b/packages/schema-core/src/schemas/union.ts @@ -1,8 +1,14 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + isUnknown as isAny, + Object_assign, + union as union$, + URI, +} from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from './types.js' -import { is } from './predicates.js' +import type { Entry, Schema, SchemaLike } from '../namespace.js' export function union(...schemas: S): union export function union }>(...schemas: S): union @@ -24,8 +30,8 @@ export namespace union { let userExtensions: Record = { //<%= Extensions %> } - const anyOf = xs.every(isPredicate) ? is.union(xs.map(safeCoerce)) : is.unknown - function UnionSchema(src: unknown): src is unknown { return anyOf(src) } + const predicate = xs.every(_isPredicate) ? union$(xs) : isAny + function UnionSchema(src: unknown): src is unknown { return predicate(src) } UnionSchema.tag = URI.union UnionSchema.def = xs Object_assign(UnionSchema, union.userDefinitions) diff --git a/packages/schema-core/src/unknown.ts b/packages/schema-core/src/schemas/unknown.ts similarity index 100% rename from packages/schema-core/src/unknown.ts rename to packages/schema-core/src/schemas/unknown.ts diff --git a/packages/schema-core/src/void.ts b/packages/schema-core/src/schemas/void.ts similarity index 100% rename from packages/schema-core/src/void.ts rename to packages/schema-core/src/schemas/void.ts diff --git a/packages/schema-core/src/types.ts b/packages/schema-core/src/types.ts index ddb59da3..3a62fd38 100644 --- a/packages/schema-core/src/types.ts +++ b/packages/schema-core/src/types.ts @@ -1,9 +1,9 @@ import type { TypeError, URI, symbol } from '@traversable/registry' -import type { unknown } from './unknown.js' -import type { of } from './of.js' +import type { unknown } from './schemas/unknown.js' +import type { of } from './schemas/of.js' import type { nonnullable } from './nonnullable.js' import type { OPT, REQ } from './label.js' -import type { optional } from './optional.js' +import type { optional } from './schemas/optional.js' export interface top { tag: URI.top, readonly _type: unknown, def: this['_type'] } export interface bottom { tag: URI.bottom, readonly _type: never, def: this['_type'] } diff --git a/packages/schema-core/test/bounded.test.ts b/packages/schema-core/test/bounded.test.ts index b4945b57..ae6a2fc5 100644 --- a/packages/schema-core/test/bounded.test.ts +++ b/packages/schema-core/test/bounded.test.ts @@ -1,59 +1,8 @@ import * as vi from 'vitest' -import { t, __within as within, __withinBig as withinBig } from '@traversable/schema-core' +import { t } from '@traversable/schema-core' import { fc, test } from '@fast-check/vitest' vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-core/bounded❳', () => { - vi.it('〖⛳️〗‹ ❲within❳', () => { - // SUCCESS - vi.assert.isTrue(within({ gt: 0 })(1)) - vi.assert.isTrue(within({ gt: 0, lt: 2 })(1)) - vi.assert.isTrue(within({ lt: 2 })(1)) - // FAILURE - vi.assert.isFalse(within({ gt: 0 })(0)) - vi.assert.isFalse(within({ gt: 0, lt: 0 })(0)) - vi.assert.isFalse(within({ lt: 0 })(0)) - /* @ts-expect-error */ - vi.assert.throws(() => within({ gt: '' })(0.5)) - }) - - vi.it('〖⛳️〗‹ ❲withinBig❳', () => { - // SUCCESS - vi.assert.isTrue(withinBig({})(1)) - vi.assert.isTrue(withinBig({ lte: 1 })(1)) - vi.assert.isTrue(withinBig({ gte: 1 })(1)) - vi.assert.isTrue(withinBig({ lt: 2 })(1)) - vi.assert.isTrue(withinBig({ gt: 0 })(1)) - vi.assert.isTrue(withinBig({ gt: 0, lt: 2 })(1)) - vi.assert.isTrue(withinBig({ gt: 0, lte: 1 })(1)) - vi.assert.isTrue(withinBig({ gte: 0, lt: 2 })(1)) - vi.assert.isTrue(withinBig({ gte: 0, lte: 1 })(1)) - vi.assert.isTrue(withinBig({ gt: 0, lt: 2, gte: 1 })(1)) - vi.assert.isTrue(withinBig({ gt: 0, lte: 1, gte: 1 })(1)) - vi.assert.isTrue(withinBig({ gte: 1, lt: 2, lte: 1 })(1)) - vi.assert.isTrue(withinBig({ gte: 1, lte: 1, lt: 2 })(1)) - vi.assert.isTrue(withinBig({ gte: 1, lte: 1, gt: 0 })(1)) - vi.assert.isTrue(withinBig({ gte: 1, lt: 2, gt: 0 })(1)) - vi.assert.isTrue(withinBig({ gte: 1, lte: 1, lt: 2, gt: 0 })(1)) - // FAILURE - vi.assert.isFalse(withinBig({ lte: 0 })(1)) - vi.assert.isFalse(withinBig({ gte: 1 })(-1)) - vi.assert.isFalse(withinBig({ lt: 0 })(1)) - vi.assert.isFalse(withinBig({ gt: 2 })(-1)) - vi.assert.isFalse(withinBig({ gt: 0, lt: 2 })(-1)) - vi.assert.isFalse(withinBig({ gt: 0, lte: 1 })(-1)) - vi.assert.isFalse(withinBig({ gte: 0, lt: 2 })(-1)) - vi.assert.isFalse(withinBig({ gte: 0, lte: 1 })(-1)) - vi.assert.isFalse(withinBig({ gt: 0, lt: 2, gte: 1 })(-1)) - vi.assert.isFalse(withinBig({ gt: 0, lte: 1, gte: 1 })(-1)) - vi.assert.isFalse(withinBig({ gte: 1, lt: 2, lte: 1 })(-1)) - vi.assert.isFalse(withinBig({ gte: 1, lte: 1, lt: 2 })(-1)) - vi.assert.isFalse(withinBig({ gte: 1, lte: 1, gt: 0 })(-1)) - vi.assert.isFalse(withinBig({ gte: 1, lt: 2, gt: 0 })(-1)) - vi.assert.isFalse(withinBig({ gte: 1, lte: 1, lt: 2, gt: 0 })(-1)) - /* @ts-expect-error */ - vi.assert.throws(() => withinBig({ gt: '' })(0.5)) - }) - vi.describe('〖⛳️〗‹‹ ❲t.integer❳', () => { vi.it('〖⛳️〗‹ ❲t.integer.min(x).max(y)❳', () => { let ex_01 = t.integer.min(0).max(1) diff --git a/packages/schema-core/test/schema.test.ts b/packages/schema-core/test/schema.test.ts index 790f9f88..e6337808 100644 --- a/packages/schema-core/test/schema.test.ts +++ b/packages/schema-core/test/schema.test.ts @@ -12,7 +12,6 @@ import { recurse, t, clone, - __carryover as carryover, } from '@traversable/schema-core' import * as Seed from './seed.js' @@ -716,11 +715,6 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema#config❳', () => { vi.assert.isFalse(t.isUnary({})) }) - // vi.it('〖⛳️〗› ❲~replaceBooleanConstructor❳', () => { - // vi.assert.equal(replaceBooleanConstructor(globalThis.Boolean), t.nonnullable) - // vi.assert.equal(replaceBooleanConstructor(t.string), t.string) - // }) - vi.it('〖⛳️〗› ❲clone❳', () => { vi.assert.isFunction(clone(t.number)) vi.assert.isTrue(clone(t.number)(2)) @@ -735,10 +729,4 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema#config❳', () => { vi.assert.isTrue(t.isBoundableTag(URI.string)) vi.assert.isFalse(t.isBoundableTag(URI.null)) }) - - vi.it('〖⛳️〗› ❲~carryover❳', () => { - vi.assert.deepEqual(carryover({}), {}) - }) - /// coverage /// - ////////////////////// }) diff --git a/packages/schema-generator/package.json b/packages/schema-generator/package.json index cf55500c..03f32b88 100644 --- a/packages/schema-generator/package.json +++ b/packages/schema-generator/package.json @@ -42,7 +42,7 @@ "clean": "pnpm run \"/^clean:.*/\"", "clean:build": "rm -rf .tsbuildinfo dist build", "clean:deps": "rm -rf node_modules", - "postinstall": "pnpm dlx tsx ./src/cli.ts", + "_postinstall": "pnpm dlx tsx ./src/cli.ts", "test": "vitest" }, "peerDependencies": { diff --git a/packages/schema-generator/src/__generated__/__manifest__.ts b/packages/schema-generator/src/__generated__/__manifest__.ts index 31647f52..541a8247 100644 --- a/packages/schema-generator/src/__generated__/__manifest__.ts +++ b/packages/schema-generator/src/__generated__/__manifest__.ts @@ -38,7 +38,7 @@ export default { "clean": "pnpm run \"/^clean:.*/\"", "clean:build": "rm -rf .tsbuildinfo dist build", "clean:deps": "rm -rf node_modules", - "postinstall": "pnpm dlx tsx ./src/cli.ts", + "_postinstall": "pnpm dlx tsx ./src/cli.ts", "test": "vitest" }, "peerDependencies": { diff --git a/packages/schema-generator/test/test-data/array/core.ts b/packages/schema-generator/test/test-data/array/core.ts index 0767526f..e4580dc7 100644 --- a/packages/schema-generator/test/test-data/array/core.ts +++ b/packages/schema-generator/test/test-data/array/core.ts @@ -1,43 +1,61 @@ -import type { Integer, Unknown } from '@traversable/registry' +import type { + Bounds, + Integer, + Unknown, +} from '@traversable/registry' import { Array_isArray, + array as arrayOf, bindUserExtensions, - isPredicate, + carryover, + within, + _isPredicate, + has, Math_max, Math_min, Number_isSafeInteger, Object_assign, - safeCoerce, URI, } from '@traversable/registry' +import type { Guarded, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +import type { of } from '../of/index.js' -import type { Bounds } from '@traversable/schema-core' -import { t, __carryover as carryover, __within as within } from '@traversable/schema-core' +/** @internal */ +function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { + return Object_assign(function BoundedArraySchema(u: unknown) { + return Array_isArray(u) && within(bounds)(u.length) + }, carry, array(schema)) +} export interface array extends array.core { //<%= Types %> } -export function array(schema: S): array -export function array(schema: S): array> +export function array(schema: S, readonly: 'readonly'): readonlyArray +export function array(schema: S): array +export function array(schema: S): array>> export function array(schema: S): array { return array.def(schema) } export namespace array { - export let userDefinitions = { + export let userDefinitions: Record = { //<%= Definitions %> } as array export function def(x: S, prev?: array): array export function def(x: S, prev?: unknown): array export function def(x: S, prev?: array): array + /* v8 ignore next 1 */ export function def(x: unknown, prev?: unknown): {} { - let userDefinitions: Record = { + let userExtensions: Record = { //<%= Extensions %> } - const arrayPredicate = isPredicate(x) ? array$(safeCoerce(x)) : Array_isArray - function ArraySchema(src: unknown) { return arrayPredicate(src) } + const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray + function ArraySchema(src: unknown) { return predicate(src) } ArraySchema.tag = URI.array ArraySchema.def = x ArraySchema.min = function arrayMin(minLength: Min) { @@ -63,10 +81,10 @@ export namespace array { { minLength, maxLength }, ) } - if (t.has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength - if (t.has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength + if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength + if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength Object_assign(ArraySchema, userDefinitions) - return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userDefinitions)) + return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) } } @@ -74,7 +92,7 @@ export declare namespace array { interface core { (u: this['_type'] | Unknown): u is this['_type'] tag: URI.array - def: S + get def(): S _type: S['_type' & keyof S][] minLength?: number maxLength?: number @@ -98,14 +116,13 @@ export declare namespace array { type type = never | T } -let array$ - : (f: (u: unknown) => u is unknown) => (u: unknown) => u is unknown[] - = (f: (u: unknown) => u is unknown) => (u: unknown): u is unknown[] => Array_isArray(u) && u.every(f) - -function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { - return Object_assign(function BoundedArraySchema(u: unknown) { - return Array_isArray(u) && within(bounds)(u.length) - }, carry, array(schema)) +export const readonlyArray: { + (schema: S): readonlyArray + (schema: S): readonlyArray> +} = array +export interface readonlyArray { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: ReadonlyArray } diff --git a/packages/schema-generator/test/test-data/bigint/core.ts b/packages/schema-generator/test/test-data/bigint/core.ts index 9e336a18..5c70c86c 100644 --- a/packages/schema-generator/test/test-data/bigint/core.ts +++ b/packages/schema-generator/test/test-data/bigint/core.ts @@ -1,13 +1,12 @@ -import type { Unknown } from '@traversable/registry' +import type { Bounds, Unknown } from '@traversable/registry' import { + bindUserExtensions, + carryover, Object_assign, URI, - bindUserExtensions, + withinBig as within, } from '@traversable/registry' -import type { Bounds } from '@traversable/schema-core' -import { __carryover as carryover, __withinBig as within } from '@traversable/schema-core' - export let userDefinitions: Record = { //<%= Definitions %> } diff --git a/packages/schema-generator/test/test-data/eq/core.ts b/packages/schema-generator/test/test-data/eq/core.ts index 38db9930..2cf9c9f7 100644 --- a/packages/schema-generator/test/test-data/eq/core.ts +++ b/packages/schema-generator/test/test-data/eq/core.ts @@ -1,6 +1,5 @@ import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' -import { applyOptions, bindUserExtensions, isPredicate, Object_assign, URI } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import { applyOptions, bindUserExtensions, _isPredicate, Object_assign, URI } from '@traversable/registry' export interface eq extends eq.core { //<%= Types %> @@ -21,8 +20,8 @@ export namespace eq { //<%= Extensions %> } const options = applyOptions($) - const eqGuard = isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) - function EqSchema(src: unknown) { return eqGuard(src) } + const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return predicate(src) } EqSchema.tag = URI.tag EqSchema.def = x Object_assign(EqSchema, eq.userDefinitions) diff --git a/packages/schema-generator/test/test-data/integer/core.ts b/packages/schema-generator/test/test-data/integer/core.ts index b4c7cff7..f2bf6f95 100644 --- a/packages/schema-generator/test/test-data/integer/core.ts +++ b/packages/schema-generator/test/test-data/integer/core.ts @@ -1,16 +1,15 @@ -import type { Integer, Unknown } from '@traversable/registry' +import type { Bounds, Integer, Unknown } from '@traversable/registry' import { + carryover, Math_min, Math_max, Number_isSafeInteger, Object_assign, URI, bindUserExtensions, + within, } from '@traversable/registry' -import type { Bounds } from '@traversable/schema-core' -import { __carryover as carryover, __within as within } from '@traversable/schema-core' - export let userDefinitions: Record = { //<%= Definitions %> } diff --git a/packages/schema-generator/test/test-data/intersect/core.ts b/packages/schema-generator/test/test-data/intersect/core.ts index 46395b1a..916aa265 100644 --- a/packages/schema-generator/test/test-data/intersect/core.ts +++ b/packages/schema-generator/test/test-data/intersect/core.ts @@ -1,29 +1,37 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' -import type { IntersectType } from '@traversable/schema-core' -import { t, Predicate } from '@traversable/schema-core' +import { + _isPredicate, + bindUserExtensions, + intersect as intersect$, + isUnknown as isAny, + Object_assign, + URI, +} from '@traversable/registry' -export interface intersect extends intersect.core { - //<%= Types %> -} +import type { Entry, IntersectType, Schema, SchemaLike } from '@traversable/schema-core/namespace' -export function intersect(...schemas: S): intersect -export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect export function intersect(...schemas: readonly unknown[]) { return intersect.def(schemas) } +export interface intersect extends intersect.core { + //<%= Types %> +} + export namespace intersect { export let userDefinitions: Record = { //<%= Definitions %> } as intersect export function def(xs: readonly [...T]): intersect + /* v8 ignore next 1 */ export function def(xs: readonly unknown[]): {} { let userExtensions: Record = { //<%= Extensions %> } - const allOf = xs.every(isPredicate) ? Predicate.is.intersect(xs.map(safeCoerce)) : Predicate.is.unknown - function IntersectSchema(src: unknown) { return allOf(src) } + const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny + function IntersectSchema(src: unknown) { return predicate(src) } IntersectSchema.tag = URI.intersect IntersectSchema.def = xs Object_assign(IntersectSchema, intersect.userDefinitions) @@ -35,7 +43,7 @@ export declare namespace intersect { interface core { (u: this['_type'] | Unknown): u is this['_type'] tag: URI.intersect - def: S + get def(): S _type: IntersectType } type type> = never | T diff --git a/packages/schema-generator/test/test-data/number/core.ts b/packages/schema-generator/test/test-data/number/core.ts index a9681a62..6575fcac 100644 --- a/packages/schema-generator/test/test-data/number/core.ts +++ b/packages/schema-generator/test/test-data/number/core.ts @@ -1,8 +1,13 @@ -import type { Unknown } from '@traversable/registry' -import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' - -import type { Bounds } from '@traversable/schema-core' -import { __carryover as carryover, __within as within } from '@traversable/schema-core' +import type { Bounds, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' export { number_ as number } diff --git a/packages/schema-generator/test/test-data/object/core.ts b/packages/schema-generator/test/test-data/object/core.ts index 0c8f05cb..4aa57c18 100644 --- a/packages/schema-generator/test/test-data/object/core.ts +++ b/packages/schema-generator/test/test-data/object/core.ts @@ -1,50 +1,54 @@ -import type { Force, Unknown } from '@traversable/registry' +import type { Force, SchemaOptions as Options, Unknown } from '@traversable/registry' import { - Array_isArray, applyOptions, + Array_isArray, bindUserExtensions, - isPredicate, - map, + has, + _isPredicate, Object_assign, Object_keys, - safeCoerce, + record as record$, + object as object$, + isAnyObject, + symbol, URI, } from '@traversable/registry' -import { SchemaOptions as Options } from '@traversable/schema-core' -import { t, Predicate } from '@traversable/schema-core' -interface object_ extends object_.core { - //<%= Types %> -} +import type { Entry, Optional, Required, Schema, SchemaLike } from '@traversable/schema-core/namespace' export { object_ as object } + function object_< - S extends { [x: string]: t.Schema }, - T extends { [K in keyof S]: t.Entry } + S extends { [x: string]: Schema }, + T extends { [K in keyof S]: Entry } >(schemas: S, options?: Options): object_ function object_< - S extends { [x: string]: t.Predicate }, - T extends { [K in keyof S]: t.Entry } + S extends { [x: string]: SchemaLike }, + T extends { [K in keyof S]: Entry } >(schemas: S, options?: Options): object_ -function object_(schemas: { [x: string]: t.Schema }, options?: Options) { +function object_(schemas: { [x: string]: Schema }, options?: Options) { return object_.def(schemas, options) } +interface object_ extends object_.core { + //<%= Types %> +} + namespace object_ { export let userDefinitions: Record = { //<%= Definitions %> } as object_ export function def(xs: T, $?: Options, opt?: string[]): object_ + /* v8 ignore next 1 */ export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { let userExtensions: Record = { //<%= Extensions %> } const keys = Object_keys(xs) - const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => t.optional.is(xs[k])) - const req = keys.filter((k) => !t.optional.is(xs[k])) - const objectGuard = !Predicate.record$(isPredicate)(xs) ? Predicate.is.anyObject - : Predicate.is.object(map(xs, safeCoerce), applyOptions($)) - function ObjectSchema(src: unknown) { return objectGuard(src) } + const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) + const req = keys.filter((k) => !has(symbol.optional)(xs[k])) + const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) + function ObjectSchema(src: unknown) { return predicate(src) } ObjectSchema.tag = URI.object ObjectSchema.def = xs ObjectSchema.opt = opt @@ -59,14 +63,14 @@ declare namespace object_ { (u: this['_type'] | Unknown): u is this['_type'] _type: object_.type tag: URI.object - def: S - opt: t.Optional - req: t.Required + get def(): S + opt: Optional + req: Required } type type< S, - Opt extends t.Optional = t.Optional, - Req extends t.Required = t.Required, + Opt extends Optional = Optional, + Req extends Required = Required, T = Force< & { [K in Req]-?: S[K]['_type' & keyof S[K]] } & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } diff --git a/packages/schema-generator/test/test-data/of/index.ts b/packages/schema-generator/test/test-data/of/index.ts new file mode 100644 index 00000000..8f6f9030 --- /dev/null +++ b/packages/schema-generator/test/test-data/of/index.ts @@ -0,0 +1,44 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +import type { + Entry, + Guard, + Guarded, + SchemaLike, +} from '@traversable/schema-core/namespace' + +export interface of extends of.core { + //<%= Types %> +} + +export function of(typeguard: S): Entry +export function of(typeguard: S): of +export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { + typeguard.def = typeguard + return Object_assign(typeguard, of.prototype) +} + +export namespace of { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(guard: T): of + /* v8 ignore next 6 */ + export function def(guard: T) { + function InlineSchema(src: unknown) { return guard(src) } + InlineSchema.tag = URI.inline + InlineSchema.def = guard + return InlineSchema + } +} + +export declare namespace of { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: Guarded + tag: URI.inline + get def(): S + } + type type> = never | T +} diff --git a/packages/schema-generator/test/test-data/optional/core.ts b/packages/schema-generator/test/test-data/optional/core.ts index b5f6ba04..ba7120bc 100644 --- a/packages/schema-generator/test/test-data/optional/core.ts +++ b/packages/schema-generator/test/test-data/optional/core.ts @@ -1,14 +1,25 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, has, isPredicate, Object_assign, safeCoerce, symbol, URI } from '@traversable/registry' -import { t, Predicate } from '@traversable/schema-core' +import { + bindUserExtensions, + has, + _isPredicate, + optional as optional$, + Object_assign, + symbol, + URI, + isUnknown as isAny, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export function optional(schema: S): optional +export function optional(schema: S): optional> +export function optional(schema: S): optional { return optional.def(schema) } export interface optional extends optional.core { //<%= Types %> } -export function optional(schema: S): optional -export function optional(schema: S): optional> -export function optional(schema: S): optional { return optional.def(schema) } export namespace optional { export let userDefinitions: Record = { //<%= Definitions %> @@ -18,26 +29,27 @@ export namespace optional { let userExtensions: Record = { //<%= Extensions %> } - const optionalGuard = isPredicate(x) ? Predicate.is.optional(safeCoerce(x)) : (_: unknown) => true - function OptionalSchema(src: unknown) { return optionalGuard(src) } + const predicate = _isPredicate(x) ? optional$(x) : isAny + function OptionalSchema(src: unknown) { return predicate(src) } OptionalSchema.tag = URI.optional OptionalSchema.def = x OptionalSchema[symbol.optional] = 1 - Object_assign(OptionalSchema, optional.userDefinitions) + Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) } export const is - : (u: unknown) => u is optional - = has('tag', (u) => u === URI.optional) as never + : (u: unknown) => u is optional + /* v8 ignore next 1 */ + = has('tag', (u) => u === URI.optional) } export declare namespace optional { interface core { (u: this['_type'] | Unknown): u is this['_type'] tag: URI.optional + _type: undefined | S['_type' & keyof S] def: S [symbol.optional]: number - _type: undefined | S['_type' & keyof S] } export type type = never | T } diff --git a/packages/schema-generator/test/test-data/record/core.ts b/packages/schema-generator/test/test-data/record/core.ts index fc120878..b5838df0 100644 --- a/packages/schema-generator/test/test-data/record/core.ts +++ b/packages/schema-generator/test/test-data/record/core.ts @@ -1,28 +1,37 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' -import { t, Predicate } from '@traversable/schema-core' +import { + isAnyObject, + record as record$, + bindUserExtensions, + _isPredicate, + Object_assign, + URI, +} from '@traversable/registry' -export interface record extends record.core { - //<%= Types %> -} +import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' -export function record(schema: S): record -export function record(schema: S): record> -export function record(schema: t.Schema) { +export function record(schema: S): record +export function record(schema: S): record> +export function record(schema: Schema) { return record.def(schema) } +export interface record extends record.core { + //<%= Types %> +} + export namespace record { export let userDefinitions: Record = { //<%= Definitions %> } export function def(x: T): record + /* v8 ignore next 1 */ export function def(x: unknown): {} { let userExtensions: Record = { //<%= Extensions %> } - const recordGuard = isPredicate(x) ? Predicate.is.record(safeCoerce(x)) : Predicate.is.anyObject - function RecordSchema(src: unknown) { return recordGuard(src) } + const predicate = _isPredicate(x) ? record$(x) : isAnyObject + function RecordSchema(src: unknown) { return predicate(src) } RecordSchema.tag = URI.record RecordSchema.def = x Object_assign(RecordSchema, record.userDefinitions) @@ -34,7 +43,7 @@ export declare namespace record { interface core { (u: this['_type'] | Unknown): u is this['_type'] tag: URI.record - def: S + get def(): S _type: Record } export type type> = never | T diff --git a/packages/schema-generator/test/test-data/string/core.ts b/packages/schema-generator/test/test-data/string/core.ts index 266e5103..4791ab8a 100644 --- a/packages/schema-generator/test/test-data/string/core.ts +++ b/packages/schema-generator/test/test-data/string/core.ts @@ -1,8 +1,14 @@ -import type { Integer, Unknown } from '@traversable/registry' -import { Math_min, Math_max, Object_assign, URI, bindUserExtensions } from '@traversable/registry' +import type { Bounds, Integer, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' -import type { Bounds } from '@traversable/schema-core' -import { __carryover as carryover, __within as within } from '@traversable/schema-core' interface string_ extends string_.core { //<%= Types %> diff --git a/packages/schema-generator/test/test-data/tuple/core.ts b/packages/schema-generator/test/test-data/tuple/core.ts index 1c084ea9..2b277799 100644 --- a/packages/schema-generator/test/test-data/tuple/core.ts +++ b/packages/schema-generator/test/test-data/tuple/core.ts @@ -1,62 +1,66 @@ import type { SchemaOptions as Options, TypeError, - Unknown, + Unknown } from '@traversable/registry' import { + Array_isArray, bindUserExtensions, getConfig, - isPredicate, + has, + _isPredicate, Object_assign, parseArgs, - safeCoerce, + symbol, + tuple as tuple$, URI, } from '@traversable/registry' import type { + Entry, FirstOptionalItem, + invalid, + Schema, + SchemaLike, TupleType, - ValidateTuple, -} from '@traversable/schema-core' -import { - t, - Predicate, -} from '@traversable/schema-core' + ValidateTuple +} from '@traversable/schema-core/namespace' -interface tuple extends tuple.core { - //<%= Types %> -} +import type { optional } from '../optional/core.js' export { tuple } -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> -function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...args: [...t.Predicate[]] | [...t.Predicate[], Options]) { + +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { return tuple.def(...parseArgs(getConfig().schema, args)) } +interface tuple extends tuple.core { + //<%= Types %> +} + namespace tuple { export let userDefinitions: Record = { //<%= Definitions %> } as tuple - export type type> = never | T export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple + /* v8 ignore next 1 */ export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { let userExtensions: Record = { //<%= Extensions %> } - const opt = opt_ || xs.findIndex(t.optional.is) + const opt = opt_ || xs.findIndex(has(symbol.optional)) const options = { - ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(t.optional.is) + ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) } satisfies tuple.InternalOptions - const tupleGuard = !xs.every(isPredicate) - ? Predicate.is.anyArray - : Predicate.is.tuple(options)(xs.map(safeCoerce)) - function TupleSchema(src: unknown) { return tupleGuard(src) } + const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) + function TupleSchema(src: unknown) { return predicate(src) } TupleSchema.tag = URI.tuple TupleSchema.def = xs TupleSchema.opt = opt @@ -64,16 +68,19 @@ namespace tuple { return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) } } + declare namespace tuple { interface core { (u: this['_type'] | Unknown): u is this['_type'] tag: URI.tuple - def: S _type: TupleType opt: FirstOptionalItem + def: S } - type validate = ValidateTuple> - type from - = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? t.invalid> : V[I] } : T + type type> = never | T type InternalOptions = { minLength?: number } + type validate = ValidateTuple> + + type from + = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T } diff --git a/packages/schema-generator/test/test-data/union/core.ts b/packages/schema-generator/test/test-data/union/core.ts index abedba6a..33698c38 100644 --- a/packages/schema-generator/test/test-data/union/core.ts +++ b/packages/schema-generator/test/test-data/union/core.ts @@ -1,40 +1,50 @@ import type { Unknown } from '@traversable/registry' -import { bindUserExtensions, isPredicate, Object_assign, safeCoerce, URI } from '@traversable/registry' -import { t, Predicate } from '@traversable/schema-core' +import { + _isPredicate, + bindUserExtensions, + isUnknown as isAny, + Object_assign, + union as union$, + URI, +} from '@traversable/registry' -export interface union extends union.core { - //<%= Types %> -} +import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' -export function union(...schemas: S): union -export function union }>(...schemas: S): union +export function union(...schemas: S): union +export function union }>(...schemas: S): union export function union(...schemas: unknown[]) { return union.def(schemas) } +export interface union extends union.core { + //<%= Types %> +} + export namespace union { export let userDefinitions: Record = { //<%= Definitions %> } as Partial> export function def(xs: T): union + /* v8 ignore next 1 */ export function def(xs: unknown[]) { let userExtensions: Record = { //<%= Extensions %> } - const anyOf = xs.every(isPredicate) ? Predicate.is.union(xs.map(safeCoerce)) : Predicate.is.unknown - function UnionSchema(src: unknown): src is unknown { return anyOf(src) } + const predicate = xs.every(_isPredicate) ? union$(xs) : isAny + function UnionSchema(src: unknown): src is unknown { return predicate(src) } UnionSchema.tag = URI.union UnionSchema.def = xs Object_assign(UnionSchema, union.userDefinitions) return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) } } + export declare namespace union { interface core { (u: this['_type'] | Unknown): u is this['_type'] tag: URI.union - def: S _type: union.type + get def(): S } type type = never | T } diff --git a/packages/schema/README.md b/packages/schema/README.md new file mode 100644 index 00000000..3e533d99 --- /dev/null +++ b/packages/schema/README.md @@ -0,0 +1,38 @@ +
+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘀𝗰𝗵𝗲𝗺𝗮

+
+ +

+ TODO: write me +

+ +
+ NPM Version +   + TypeScript +   + Static Badge +   + npm +   +
+ +
+ npm bundle size (scoped) +   + Static Badge +   + Static Badge +   +
+ +
+ Demo (StackBlitz) +   •   + TypeScript Playground +   •   + npm +
+
+
+
diff --git a/packages/schema/package.json b/packages/schema/package.json new file mode 100644 index 00000000..beb4b9a9 --- /dev/null +++ b/packages/schema/package.json @@ -0,0 +1,58 @@ +{ + "name": "@traversable/schema", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/schema" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:schemas": "pnpm dlx tsx ./src/build.ts", + "build:schemas:watch": "pnpm dlx tsx --watch ./src/build.ts", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/derive-codec": "workspace:^", + "@traversable/derive-equals": "workspace:^", + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^", + "@traversable/schema-generator": "workspace:^", + "@traversable/schema-to-json-schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^" + }, + "devDependencies": { + "@traversable/derive-codec": "workspace:^", + "@traversable/derive-equals": "workspace:^", + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^", + "@traversable/schema-generator": "workspace:^", + "@traversable/schema-to-json-schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^" + } +} diff --git a/packages/schema/src/__generated__/__manifest__.ts b/packages/schema/src/__generated__/__manifest__.ts new file mode 100644 index 00000000..c23c1f93 --- /dev/null +++ b/packages/schema/src/__generated__/__manifest__.ts @@ -0,0 +1,58 @@ +export default { + "name": "@traversable/schema", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/schema" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:schemas": "pnpm dlx tsx ./src/build.ts", + "build:schemas:watch": "pnpm dlx tsx --watch ./src/build.ts", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/derive-codec": "workspace:^", + "@traversable/derive-equals": "workspace:^", + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^", + "@traversable/schema-generator": "workspace:^", + "@traversable/schema-to-json-schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^" + }, + "devDependencies": { + "@traversable/derive-codec": "workspace:^", + "@traversable/derive-equals": "workspace:^", + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^", + "@traversable/schema-generator": "workspace:^", + "@traversable/schema-to-json-schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^" + } +} as const \ No newline at end of file diff --git a/packages/schema/src/build.ts b/packages/schema/src/build.ts new file mode 100755 index 00000000..0d8e0949 --- /dev/null +++ b/packages/schema/src/build.ts @@ -0,0 +1,109 @@ +#!/usr/bin/env pnpm dlx tsx +import * as path from 'node:path' +import * as fs from 'node:fs' +import { fn } from '@traversable/registry' + +let LIBS = path.join(path.resolve(), 'node_modules', '@traversable') + +let SCHEMA_WHITELIST = [ + 'of', + 'eq', + // + 'never', + 'any', + 'unknown', + 'void', + 'null', + 'undefined', + 'symbol', + 'boolean', + 'integer', + 'bigint', + 'number', + 'string', + // + 'optional', + 'array', + 'record', + 'union', + 'intersect', + 'tuple', + 'object', +] + +let LIB_BLACKLIST = [ + 'registry', +] as const satisfies any[] + +let LIB_WHITELIST = [ + 'schema-core', +] as const satisfies any[] + +let FILE_BLACKLIST = [ + 'README.md', +] as const satisfies any[] + +let SCHEMAS_DIR + +let PATH = { + sourcesDir: path.join(path.resolve(), 'node_modules', '@traversable'), + target: path.join(path.resolve(), 'src', 'schemas'), +} + +// FROM: +// import type { Guarded, Schema, SchemaLike } from '../namespace.js' + +// TO: +// import type { Guarded, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +let RelativeImport = { + from: /(\.\.\/)namespace.js/g, + to: '@traversable/schema-core/namespace' +} + +console.log(`'../namespace.js'`.replace(RelativeImport.from, RelativeImport.to)) + +function buildSchemas() { + if (!fs.existsSync(PATH.target)) { fs.mkdirSync(PATH.target) } + + return fs.readdirSync(PATH.sourcesDir, { withFileTypes: true }) + .filter(({ name }) => !LIB_BLACKLIST.includes(name as never) && LIB_WHITELIST.includes(name as never)) + .map( + ({ name, parentPath }) => fn.pipe( + path.join(parentPath, name, 'src', 'schemas'), + (absolutePath) => fs.readdirSync(absolutePath, { withFileTypes: true }), + (schemaPaths) => schemaPaths.map(({ name, parentPath }) => [ + // name.endsWith('.ts') ? name.slice(0, -'.ts'.length) : name, + path.join(PATH.target, name), + fs.readFileSync(path.join(parentPath, name)).toString('utf8') + ] satisfies [any, any]), + fn.map( + fn.flow( + ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.from, RelativeImport.to)] satisfies [any, any], + ([filePath, content]) => fs.writeFileSync(filePath, content)), + ) + + // xs => Object.fromEntries(xs), + + // (dirPaths) => dirPaths.filter((dirPath) => dirPath.endsWith('.ts')), + // (path, { withFileTypes: true }), + // fn.map(({ name, parentPath, isFile }) => name + parentPath) + + ) + ) + + + + +} + +console.log('OUT', buildSchemas()) + + +/** + * ## TODO + * + * - [ ] Pull the .ts files out of `@traversable/schema-core` + */ + +export const TEST: 1 = 1 diff --git a/packages/schema/src/exports.ts b/packages/schema/src/exports.ts new file mode 100644 index 00000000..f28474c5 --- /dev/null +++ b/packages/schema/src/exports.ts @@ -0,0 +1 @@ +export * from './version.js' diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts new file mode 100644 index 00000000..410a4bcb --- /dev/null +++ b/packages/schema/src/index.ts @@ -0,0 +1 @@ +export * from './exports.js' diff --git a/packages/schema/src/schemas/any.ts b/packages/schema/src/schemas/any.ts new file mode 100644 index 00000000..877f92af --- /dev/null +++ b/packages/schema/src/schemas/any.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { any_ as any } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface any_ extends any_.core { + //<%= Types %> +} + +function AnySchema(src: unknown): src is any { return true } +AnySchema.tag = URI.any +AnySchema.def = void 0 as any + +const any_ = Object_assign( + AnySchema, + userDefinitions, +) as any_ + +Object_assign(any_, userExtensions) + +declare namespace any_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.any + _type: any + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/array.ts b/packages/schema/src/schemas/array.ts new file mode 100644 index 00000000..0e73fc5f --- /dev/null +++ b/packages/schema/src/schemas/array.ts @@ -0,0 +1,128 @@ +import type { + Bounds, + Integer, + Unknown, +} from '@traversable/registry' +import { + Array_isArray, + array as arrayOf, + bindUserExtensions, + carryover, + within, + _isPredicate, + has, + Math_max, + Math_min, + Number_isSafeInteger, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Guarded, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +import type { of } from './of.js' + +/** @internal */ +function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { + return Object_assign(function BoundedArraySchema(u: unknown) { + return Array_isArray(u) && within(bounds)(u.length) + }, carry, array(schema)) +} + +export interface array extends array.core { + //<%= Types %> +} + +export function array(schema: S, readonly: 'readonly'): readonlyArray +export function array(schema: S): array +export function array(schema: S): array>> +export function array(schema: S): array { + return array.def(schema) +} + +export namespace array { + export let userDefinitions: Record = { + //<%= Definitions %> + } as array + export function def(x: S, prev?: array): array + export function def(x: S, prev?: unknown): array + export function def(x: S, prev?: array): array + /* v8 ignore next 1 */ + export function def(x: unknown, prev?: unknown): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray + function ArraySchema(src: unknown) { return predicate(src) } + ArraySchema.tag = URI.array + ArraySchema.def = x + ArraySchema.min = function arrayMin(minLength: Min) { + return Object_assign( + boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), + { minLength }, + ) + } + ArraySchema.max = function arrayMax(maxLength: Max) { + return Object_assign( + boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), + { maxLength }, + ) + } + ArraySchema.between = function arrayBetween( + min: Min, + max: Max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max) + ) { + return Object_assign( + boundedArray(x, { gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) + } + if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength + if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength + Object_assign(ArraySchema, userDefinitions) + return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) + } +} + +export declare namespace array { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.array + get def(): S + _type: S['_type' & keyof S][] + minLength?: number + maxLength?: number + min>(minLength: Min): array.Min + max>(maxLength: Max): array.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> + } + type Min + = [Self] extends [{ maxLength: number }] + ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> + : array.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> + : array.max + ; + interface min extends array { minLength: Min } + interface max extends array { maxLength: Max } + interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } + type type = never | T +} + +export const readonlyArray: { + (schema: S): readonlyArray + (schema: S): readonlyArray> +} = array +export interface readonlyArray { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: ReadonlyArray +} diff --git a/packages/schema/src/schemas/bigint.ts b/packages/schema/src/schemas/bigint.ts new file mode 100644 index 00000000..f7d55261 --- /dev/null +++ b/packages/schema/src/schemas/bigint.ts @@ -0,0 +1,99 @@ +import type { Bounds, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Object_assign, + URI, + withinBig as within, +} from '@traversable/registry' + +export { bigint_ as bigint } + +/** @internal */ +function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedBigIntSchema(u: unknown) { + return bigint_(u) && within(bounds)(u) + }, carry, bigint_) +} + +interface bigint_ extends bigint_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function BigIntSchema(src: unknown) { return typeof src === 'bigint' } +BigIntSchema.tag = URI.bigint +BigIntSchema.def = 0n + +const bigint_ = Object_assign( + BigIntSchema, + userDefinitions, +) as bigint_ + +bigint_.min = function bigIntMin(minimum) { + return Object_assign( + boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +bigint_.max = function bigIntMax(maximum) { + return Object_assign( + boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +bigint_.between = function bigIntBetween( + min, + max, + minimum = (max < min ? max : min), + maximum = (max < min ? min : max), +) { + return Object_assign( + boundedBigInt({ gte: minimum, lte: maximum }), + { minimum, maximum } + ) +} + +Object_assign( + bigint_, + bindUserExtensions(bigint_, userExtensions), +) + +declare namespace bigint_ { + interface core extends bigint_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: bigint + tag: URI.bigint + get def(): this['_type'] + minimum?: bigint + maximum?: bigint + } + type Min + = [Self] extends [{ maximum: bigint }] + ? bigint_.between<[min: X, max: Self['maximum']]> + : bigint_.min + ; + type Max + = [Self] extends [{ minimum: bigint }] + ? bigint_.between<[min: Self['minimum'], max: X]> + : bigint_.max + ; + interface methods { + min(minimum: Min): bigint_.Min + max(maximum: Max): bigint_.Max + between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> + } + interface min extends bigint_ { minimum: Min } + interface max extends bigint_ { maximum: Max } + interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } +} + diff --git a/packages/schema/src/schemas/boolean.ts b/packages/schema/src/schemas/boolean.ts new file mode 100644 index 00000000..c89adf4b --- /dev/null +++ b/packages/schema/src/schemas/boolean.ts @@ -0,0 +1,37 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { boolean_ as boolean } + +interface boolean_ extends boolean_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } + +BooleanSchema.tag = URI.boolean +BooleanSchema.def = false + +const boolean_ = Object_assign( + BooleanSchema, + userDefinitions, +) as boolean_ + +Object_assign(boolean_, userExtensions) + +declare namespace boolean_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.boolean + _type: boolean + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/eq.ts b/packages/schema/src/schemas/eq.ts new file mode 100644 index 00000000..d701a0f6 --- /dev/null +++ b/packages/schema/src/schemas/eq.ts @@ -0,0 +1,41 @@ +import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' +import { applyOptions, bindUserExtensions, _isPredicate, Object_assign, URI } from '@traversable/registry' + +export function eq>(value: V, options?: Options): eq> +export function eq(value: V, options?: Options): eq +export function eq(value: V, options?: Options): eq { + return eq.def(value, options) +} + +export interface eq extends eq.core { + //<%= Types %> +} + +export namespace eq { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(value: T, options?: Options): eq + /* v8 ignore next 1 */ + export function def(x: T, $?: Options): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const options = applyOptions($) + const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return predicate(src) } + EqSchema.tag = URI.eq + EqSchema.def = x + Object_assign(EqSchema, eq.userDefinitions) + return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) + } +} + +export declare namespace eq { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.eq + _type: V + get def(): V + } +} diff --git a/packages/schema/src/schemas/integer.ts b/packages/schema/src/schemas/integer.ts new file mode 100644 index 00000000..2969e5a1 --- /dev/null +++ b/packages/schema/src/schemas/integer.ts @@ -0,0 +1,100 @@ +import type { Bounds, Integer, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Number_isSafeInteger, + Object_assign, + URI, + within, +} from '@traversable/registry' + + +export { integer } + +/** @internal */ +function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedIntegerSchema(u: unknown) { + return integer(u) && within(bounds)(u) + }, carry, integer) +} + +interface integer extends integer.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } +IntegerSchema.tag = URI.integer +IntegerSchema.def = 0 + +const integer = Object_assign( + IntegerSchema, + userDefinitions, +) as integer + +integer.min = function integerMin(minimum) { + return Object_assign( + boundedInteger({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +integer.max = function integerMax(maximum) { + return Object_assign( + boundedInteger({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +integer.between = function integerBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedInteger({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + integer, + bindUserExtensions(integer, userExtensions), +) + +declare namespace integer { + interface core extends integer.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.integer + get def(): this['_type'] + minimum?: number + maximum?: number + } + interface methods { + min>(minimum: Min): integer.Min + max>(maximum: Max): integer.Max + between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maximum: number }] + ? integer.between<[min: X, max: Self['maximum']]> + : integer.min + type Max + = [Self] extends [{ minimum: number }] + ? integer.between<[min: Self['minimum'], max: X]> + : integer.max + interface min extends integer { minimum: Min } + interface max extends integer { maximum: Max } + interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } +} diff --git a/packages/schema/src/schemas/intersect.ts b/packages/schema/src/schemas/intersect.ts new file mode 100644 index 00000000..916aa265 --- /dev/null +++ b/packages/schema/src/schemas/intersect.ts @@ -0,0 +1,50 @@ +import type { Unknown } from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + intersect as intersect$, + isUnknown as isAny, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Entry, IntersectType, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: readonly unknown[]) { + return intersect.def(schemas) +} + +export interface intersect extends intersect.core { + //<%= Types %> +} + +export namespace intersect { + export let userDefinitions: Record = { + //<%= Definitions %> + } as intersect + export function def(xs: readonly [...T]): intersect + /* v8 ignore next 1 */ + export function def(xs: readonly unknown[]): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny + function IntersectSchema(src: unknown) { return predicate(src) } + IntersectSchema.tag = URI.intersect + IntersectSchema.def = xs + Object_assign(IntersectSchema, intersect.userDefinitions) + return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) + } +} + +export declare namespace intersect { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.intersect + get def(): S + _type: IntersectType + } + type type> = never | T +} diff --git a/packages/schema/src/schemas/never.ts b/packages/schema/src/schemas/never.ts new file mode 100644 index 00000000..a0077281 --- /dev/null +++ b/packages/schema/src/schemas/never.ts @@ -0,0 +1,37 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { never_ as never } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface never_ extends never_.core { + //<%= Types %> +} + +function NeverSchema(src: unknown): src is never { return false } +NeverSchema.tag = URI.never; +NeverSchema.def = void 0 as never + +const never_ = Object_assign( + NeverSchema, + userDefinitions, +) as never_ + +Object_assign(never_, userExtensions) + +export declare namespace never_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.never + _type: never + get def(): this['_type'] + } +} + diff --git a/packages/schema/src/schemas/null.ts b/packages/schema/src/schemas/null.ts new file mode 100644 index 00000000..befebeb5 --- /dev/null +++ b/packages/schema/src/schemas/null.ts @@ -0,0 +1,39 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { null_ as null, null_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface null_ extends null_.core { + //<%= Types %> +} + +function NullSchema(src: unknown): src is null { return src === null } +NullSchema.def = null +NullSchema.tag = URI.null + +const null_ = Object_assign( + NullSchema, + userDefinitions, +) as null_ + +Object_assign( + null_, + userExtensions, +) + +declare namespace null_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.null + _type: null + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/number.ts b/packages/schema/src/schemas/number.ts new file mode 100644 index 00000000..5972ae85 --- /dev/null +++ b/packages/schema/src/schemas/number.ts @@ -0,0 +1,139 @@ +import type { Bounds, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' + + +export { number_ as number } + +interface number_ extends number_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function NumberSchema(src: unknown) { return typeof src === 'number' } +NumberSchema.tag = URI.number +NumberSchema.def = 0 + +const number_ = Object_assign( + NumberSchema, + userDefinitions, +) as number_ + +number_.min = function numberMin(minimum) { + return Object_assign( + boundedNumber({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +number_.max = function numberMax(maximum) { + return Object_assign( + boundedNumber({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +number_.moreThan = function numberMoreThan(exclusiveMinimum) { + return Object_assign( + boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), + { exclusiveMinimum }, + ) +} +number_.lessThan = function numberLessThan(exclusiveMaximum) { + return Object_assign( + boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), + { exclusiveMaximum }, + ) +} +number_.between = function numberBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedNumber({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + number_, + bindUserExtensions(number_, userExtensions), +) + +function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedNumberSchema(u: unknown) { + return typeof u === 'number' && within(bounds)(u) + }, carry, number_) +} + +declare namespace number_ { + interface core extends number_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.number + get def(): this['_type'] + minimum?: number + maximum?: number + exclusiveMinimum?: number + exclusiveMaximum?: number + } + interface methods { + min(minimum: Min): number_.Min + max(maximum: Max): number_.Max + moreThan(moreThan: Min): ExclusiveMin + lessThan(lessThan: Max): ExclusiveMax + between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.between<[min: X, max: Self['maximum']]> + : number_.min + ; + type Max + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.between<[min: Self['minimum'], max: X]> + : number_.max + ; + type ExclusiveMin + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.maxStrictMin<[min: X, Self['maximum']]> + : number_.moreThan + ; + type ExclusiveMax + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.minStrictMax<[Self['minimum'], min: X]> + : number_.lessThan + ; + interface min extends number_ { minimum: Min } + interface max extends number_ { maximum: Max } + interface moreThan extends number_ { exclusiveMinimum: Min } + interface lessThan extends number_ { exclusiveMaximum: Max } + interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } + interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } + interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } + interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } +} diff --git a/packages/schema/src/schemas/object.ts b/packages/schema/src/schemas/object.ts new file mode 100644 index 00000000..4aa57c18 --- /dev/null +++ b/packages/schema/src/schemas/object.ts @@ -0,0 +1,79 @@ +import type { Force, SchemaOptions as Options, Unknown } from '@traversable/registry' +import { + applyOptions, + Array_isArray, + bindUserExtensions, + has, + _isPredicate, + Object_assign, + Object_keys, + record as record$, + object as object$, + isAnyObject, + symbol, + URI, +} from '@traversable/registry' + +import type { Entry, Optional, Required, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export { object_ as object } + +function object_< + S extends { [x: string]: Schema }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_< + S extends { [x: string]: SchemaLike }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_(schemas: { [x: string]: Schema }, options?: Options) { + return object_.def(schemas, options) +} + +interface object_ extends object_.core { + //<%= Types %> +} + +namespace object_ { + export let userDefinitions: Record = { + //<%= Definitions %> + } as object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ + /* v8 ignore next 1 */ + export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const keys = Object_keys(xs) + const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) + const req = keys.filter((k) => !has(symbol.optional)(xs[k])) + const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) + function ObjectSchema(src: unknown) { return predicate(src) } + ObjectSchema.tag = URI.object + ObjectSchema.def = xs + ObjectSchema.opt = opt + ObjectSchema.req = req + Object_assign(ObjectSchema, userDefinitions) + return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) + } +} + +declare namespace object_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: object_.type + tag: URI.object + get def(): S + opt: Optional + req: Required + } + type type< + S, + Opt extends Optional = Optional, + Req extends Required = Required, + T = Force< + & { [K in Req]-?: S[K]['_type' & keyof S[K]] } + & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } + > + > = never | T +} diff --git a/packages/schema/src/schemas/of.ts b/packages/schema/src/schemas/of.ts new file mode 100644 index 00000000..8f6f9030 --- /dev/null +++ b/packages/schema/src/schemas/of.ts @@ -0,0 +1,44 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +import type { + Entry, + Guard, + Guarded, + SchemaLike, +} from '@traversable/schema-core/namespace' + +export interface of extends of.core { + //<%= Types %> +} + +export function of(typeguard: S): Entry +export function of(typeguard: S): of +export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { + typeguard.def = typeguard + return Object_assign(typeguard, of.prototype) +} + +export namespace of { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(guard: T): of + /* v8 ignore next 6 */ + export function def(guard: T) { + function InlineSchema(src: unknown) { return guard(src) } + InlineSchema.tag = URI.inline + InlineSchema.def = guard + return InlineSchema + } +} + +export declare namespace of { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: Guarded + tag: URI.inline + get def(): S + } + type type> = never | T +} diff --git a/packages/schema/src/schemas/optional.ts b/packages/schema/src/schemas/optional.ts new file mode 100644 index 00000000..ba7120bc --- /dev/null +++ b/packages/schema/src/schemas/optional.ts @@ -0,0 +1,55 @@ +import type { Unknown } from '@traversable/registry' +import { + bindUserExtensions, + has, + _isPredicate, + optional as optional$, + Object_assign, + symbol, + URI, + isUnknown as isAny, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export function optional(schema: S): optional +export function optional(schema: S): optional> +export function optional(schema: S): optional { return optional.def(schema) } + +export interface optional extends optional.core { + //<%= Types %> +} + +export namespace optional { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): optional + export function def(x: T) { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = _isPredicate(x) ? optional$(x) : isAny + function OptionalSchema(src: unknown) { return predicate(src) } + OptionalSchema.tag = URI.optional + OptionalSchema.def = x + OptionalSchema[symbol.optional] = 1 + Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) + return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) + } + export const is + : (u: unknown) => u is optional + /* v8 ignore next 1 */ + = has('tag', (u) => u === URI.optional) +} + +export declare namespace optional { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.optional + _type: undefined | S['_type' & keyof S] + def: S + [symbol.optional]: number + } + export type type = never | T +} diff --git a/packages/schema/src/schemas/record.ts b/packages/schema/src/schemas/record.ts new file mode 100644 index 00000000..b5838df0 --- /dev/null +++ b/packages/schema/src/schemas/record.ts @@ -0,0 +1,50 @@ +import type { Unknown } from '@traversable/registry' +import { + isAnyObject, + record as record$, + bindUserExtensions, + _isPredicate, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export function record(schema: S): record +export function record(schema: S): record> +export function record(schema: Schema) { + return record.def(schema) +} + +export interface record extends record.core { + //<%= Types %> +} + +export namespace record { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): record + /* v8 ignore next 1 */ + export function def(x: unknown): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = _isPredicate(x) ? record$(x) : isAnyObject + function RecordSchema(src: unknown) { return predicate(src) } + RecordSchema.tag = URI.record + RecordSchema.def = x + Object_assign(RecordSchema, record.userDefinitions) + return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) + } +} + +export declare namespace record { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.record + get def(): S + _type: Record + } + export type type> = never | T +} diff --git a/packages/schema/src/schemas/string.ts b/packages/schema/src/schemas/string.ts new file mode 100644 index 00000000..4276ca17 --- /dev/null +++ b/packages/schema/src/schemas/string.ts @@ -0,0 +1,102 @@ +import type { Bounds, Integer, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' + +export { string_ as string } + +/** @internal */ +function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedStringSchema(u: unknown) { + return string_(u) && within(bounds)(u.length) + }, carry, string_) +} + +interface string_ extends string_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function StringSchema(src: unknown) { return typeof src === 'string' } +StringSchema.tag = URI.string +StringSchema.def = '' + +const string_ = Object_assign( + StringSchema, + userDefinitions, +) as string_ + +string_.min = function stringMinLength(minLength) { + return Object_assign( + boundedString({ gte: minLength }, carryover(this, 'minLength')), + { minLength }, + ) +} +string_.max = function stringMaxLength(maxLength) { + return Object_assign( + boundedString({ lte: maxLength }, carryover(this, 'maxLength')), + { maxLength }, + ) +} +string_.between = function stringBetween( + min, + max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max)) { + return Object_assign( + boundedString({ gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) +} + +Object_assign( + string_, + bindUserExtensions(string_, userExtensions), +) + +declare namespace string_ { + interface core extends string_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: string + tag: URI.string + get def(): this['_type'] + } + interface methods { + minLength?: number + maxLength?: number + min>(minLength: Min): string_.Min + max>(maxLength: Max): string_.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maxLength: number }] + ? string_.between<[min: Min, max: Self['maxLength']]> + : string_.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? string_.between<[min: Self['minLength'], max: Max]> + : string_.max + ; + interface min extends string_ { minLength: Min } + interface max extends string_ { maxLength: Max } + interface between extends string_ { + minLength: Bounds[0] + maxLength: Bounds[1] + } +} diff --git a/packages/schema/src/schemas/symbol.ts b/packages/schema/src/schemas/symbol.ts new file mode 100644 index 00000000..6e192b3e --- /dev/null +++ b/packages/schema/src/schemas/symbol.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { symbol_ as symbol } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface symbol_ extends symbol_.core { + //<%= Types %> +} + +function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } +SymbolSchema.tag = URI.symbol +SymbolSchema.def = Symbol() + +const symbol_ = Object_assign( + SymbolSchema, + userDefinitions, +) as symbol_ + +Object_assign(symbol_, userExtensions) + +declare namespace symbol_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.symbol + _type: symbol + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/tuple.ts b/packages/schema/src/schemas/tuple.ts new file mode 100644 index 00000000..ed906497 --- /dev/null +++ b/packages/schema/src/schemas/tuple.ts @@ -0,0 +1,86 @@ +import type { + SchemaOptions as Options, + TypeError, + Unknown +} from '@traversable/registry' + +import { + Array_isArray, + bindUserExtensions, + getConfig, + has, + _isPredicate, + Object_assign, + parseArgs, + symbol, + tuple as tuple$, + URI, +} from '@traversable/registry' + +import type { + Entry, + FirstOptionalItem, + invalid, + Schema, + SchemaLike, + TupleType, + ValidateTuple +} from '@traversable/schema-core/namespace' + +import type { optional } from './optional.js' + +export { tuple } + +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { + return tuple.def(...parseArgs(getConfig().schema, args)) +} + +interface tuple extends tuple.core { + //<%= Types %> +} + +namespace tuple { + export let userDefinitions: Record = { + //<%= Definitions %> + } as tuple + export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple + /* v8 ignore next 1 */ + export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const opt = opt_ || xs.findIndex(has(symbol.optional)) + const options = { + ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) + } satisfies tuple.InternalOptions + const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) + function TupleSchema(src: unknown) { return predicate(src) } + TupleSchema.tag = URI.tuple + TupleSchema.def = xs + TupleSchema.opt = opt + Object_assign(TupleSchema, tuple.userDefinitions) + return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) + } +} + +declare namespace tuple { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.tuple + _type: TupleType + opt: FirstOptionalItem + def: S + } + type type> = never | T + type InternalOptions = { minLength?: number } + type validate = ValidateTuple> + + type from + = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T +} diff --git a/packages/schema/src/schemas/undefined.ts b/packages/schema/src/schemas/undefined.ts new file mode 100644 index 00000000..0115f168 --- /dev/null +++ b/packages/schema/src/schemas/undefined.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { undefined_ as undefined } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface undefined_ extends undefined_.core { + //<%= Types %> +} + +function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } +UndefinedSchema.tag = URI.undefined +UndefinedSchema.def = void 0 as undefined + +const undefined_ = Object_assign( + UndefinedSchema, + userDefinitions, +) as undefined_ + +Object_assign(undefined_, userExtensions) + +declare namespace undefined_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.undefined + _type: undefined + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/union.ts b/packages/schema/src/schemas/union.ts new file mode 100644 index 00000000..33698c38 --- /dev/null +++ b/packages/schema/src/schemas/union.ts @@ -0,0 +1,50 @@ +import type { Unknown } from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + isUnknown as isAny, + Object_assign, + union as union$, + URI, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: unknown[]) { + return union.def(schemas) +} + +export interface union extends union.core { + //<%= Types %> +} + +export namespace union { + export let userDefinitions: Record = { + //<%= Definitions %> + } as Partial> + export function def(xs: T): union + /* v8 ignore next 1 */ + export function def(xs: unknown[]) { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = xs.every(_isPredicate) ? union$(xs) : isAny + function UnionSchema(src: unknown): src is unknown { return predicate(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.userDefinitions) + return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) + } +} + +export declare namespace union { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.union + _type: union.type + get def(): S + } + type type = never | T +} diff --git a/packages/schema/src/schemas/unknown.ts b/packages/schema/src/schemas/unknown.ts new file mode 100644 index 00000000..0a190db3 --- /dev/null +++ b/packages/schema/src/schemas/unknown.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { unknown_ as unknown } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface unknown_ extends unknown_.core { + //<%= Types %> +} + +function UnknownSchema(src: unknown): src is unknown { return true } +UnknownSchema.tag = URI.unknown +UnknownSchema.def = void 0 as unknown + +const unknown_ = Object_assign( + UnknownSchema, + userDefinitions, +) as unknown_ + +Object_assign(unknown_, userExtensions) + +declare namespace unknown_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.unknown + _type: unknown + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/void.ts b/packages/schema/src/schemas/void.ts new file mode 100644 index 00000000..d178213e --- /dev/null +++ b/packages/schema/src/schemas/void.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { void_ as void, void_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface void_ extends void_.core { + //<%= Types %> +} + +function VoidSchema(src: unknown): src is void { return src === void 0 } +VoidSchema.tag = URI.void +VoidSchema.def = void 0 as void + +const void_ = Object_assign( + VoidSchema, + userDefinitions, +) as void_ + +Object_assign(void_, userExtensions) + +declare namespace void_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.void + _type: void + get def(): this['_type'] + } +} diff --git a/packages/schema/src/version.ts b/packages/schema/src/version.ts new file mode 100644 index 00000000..388bbc3e --- /dev/null +++ b/packages/schema/src/version.ts @@ -0,0 +1,3 @@ +import pkg from './__generated__/__manifest__.js' +export const VERSION = `${pkg.name}@${pkg.version}` as const +export type VERSION = typeof VERSION \ No newline at end of file diff --git a/packages/schema/test/version.test.ts b/packages/schema/test/version.test.ts new file mode 100644 index 00000000..2a564622 --- /dev/null +++ b/packages/schema/test/version.test.ts @@ -0,0 +1,10 @@ +import * as vi from 'vitest' +import pkg from '../package.json' with { type: 'json' } +import { VERSION } from '@traversable/schema' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema❳', () => { + vi.it('〖⛳️〗› ❲schema#VERSION❳', () => { + const expected = `${pkg.name}@${pkg.version}` + vi.assert.equal(VERSION, expected) + }) +}) \ No newline at end of file diff --git a/packages/schema/tsconfig.build.json b/packages/schema/tsconfig.build.json new file mode 100644 index 00000000..f1e02803 --- /dev/null +++ b/packages/schema/tsconfig.build.json @@ -0,0 +1,19 @@ +{ + "extends": "./tsconfig.src.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", + "types": ["node"], + "declarationDir": "build/dts", + "outDir": "build/esm", + "stripInternal": true + }, + "references": [ + { "path": "../derive-codec" }, + { "path": "../derive-equals" }, + { "path": "../registry" }, + { "path": "../schema-core" }, + { "path": "../schema-generator" }, + { "path": "../schema-to-json-schema" }, + { "path": "../schema-to-string" } + ] +} diff --git a/packages/schema/tsconfig.json b/packages/schema/tsconfig.json new file mode 100644 index 00000000..2c291d21 --- /dev/null +++ b/packages/schema/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [], + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "tsconfig.test.json" } + ] +} diff --git a/packages/schema/tsconfig.src.json b/packages/schema/tsconfig.src.json new file mode 100644 index 00000000..bbca465c --- /dev/null +++ b/packages/schema/tsconfig.src.json @@ -0,0 +1,19 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", + "rootDir": "src", + "types": ["node"], + "outDir": "build/src" + }, + "references": [ + { "path": "../derive-codec" }, + { "path": "../derive-equals" }, + { "path": "../registry" }, + { "path": "../schema-core" }, + { "path": "../schema-generator" }, + { "path": "../schema-to-json-schema" }, + { "path": "../schema-to-string" } + ], + "include": ["src"] +} diff --git a/packages/schema/tsconfig.test.json b/packages/schema/tsconfig.test.json new file mode 100644 index 00000000..4f443a87 --- /dev/null +++ b/packages/schema/tsconfig.test.json @@ -0,0 +1,20 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", + "rootDir": "test", + "types": ["node"], + "noEmit": true + }, + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "../derive-codec" }, + { "path": "../derive-equals" }, + { "path": "../registry" }, + { "path": "../schema-core" }, + { "path": "../schema-generator" }, + { "path": "../schema-to-json-schema" }, + { "path": "../schema-to-string" } + ], + "include": ["test"] +} diff --git a/packages/schema/vite.config.ts b/packages/schema/vite.config.ts new file mode 100644 index 00000000..64dba4ad --- /dev/null +++ b/packages/schema/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import sharedConfig from '../../vite.config.js' + +const localConfig = defineConfig({}) + +export default mergeConfig(sharedConfig, localConfig) \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00f3f336..dfc05b66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -235,6 +235,31 @@ importers: packages/registry: publishDirectory: dist + packages/schema: + devDependencies: + '@traversable/derive-codec': + specifier: workspace:^ + version: link:../derive-codec/dist + '@traversable/derive-equals': + specifier: workspace:^ + version: link:../derive-equals/dist + '@traversable/registry': + specifier: workspace:^ + version: link:../registry/dist + '@traversable/schema-core': + specifier: workspace:^ + version: link:../schema-core/dist + '@traversable/schema-generator': + specifier: workspace:^ + version: link:../schema-generator/dist + '@traversable/schema-to-json-schema': + specifier: workspace:^ + version: link:../schema-to-json-schema/dist + '@traversable/schema-to-string': + specifier: workspace:^ + version: link:../schema-to-string/dist + publishDirectory: dist + packages/schema-core: dependencies: '@traversable/registry': diff --git a/symbol.object b/symbol.object new file mode 100644 index 00000000..e69de29b diff --git a/tsconfig.base.json b/tsconfig.base.json index aac589e7..94d2a9a1 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -32,26 +32,46 @@ "@traversable/derive-codec/*": ["packages/derive-codec/src/*.js"], "@traversable/derive-equals": ["packages/derive-equals/src/index.js"], "@traversable/derive-equals/*": ["packages/derive-equals/src/*.js"], - "@traversable/derive-validators": [ "packages/derive-validators/src/index.js" ], - "@traversable/derive-validators/*": [ "packages/derive-validators/src/*.js" ], + "@traversable/derive-validators": [ + "packages/derive-validators/src/index.js" + ], + "@traversable/derive-validators/*": [ + "packages/derive-validators/src/*.js" + ], "@traversable/json": ["packages/json/src/index.js"], "@traversable/json/*": ["packages/json/src/*.js"], "@traversable/registry": ["packages/registry/src/index.js"], "@traversable/registry/*": ["packages/registry/src/*.js"], + "@traversable/schema": ["packages/schema/src/index.js"], "@traversable/schema-core": ["packages/schema-core/src/index.js"], "@traversable/schema-core/*": ["packages/schema-core/src/*.js"], - "@traversable/schema-generator": [ "packages/schema-generator/src/index.js" ], + "@traversable/schema-generator": [ + "packages/schema-generator/src/index.js" + ], "@traversable/schema-generator/*": ["packages/schema-generator/*.js"], "@traversable/schema-seed": ["packages/schema-seed/src/index.js"], "@traversable/schema-seed/*": ["packages/schema-seed/src/*.js"], - "@traversable/schema-to-json-schema": [ "packages/schema-to-json-schema/src/index.js" ], - "@traversable/schema-to-json-schema/*": [ "packages/schema-to-json-schema/src/*.js" ], - "@traversable/schema-to-string": [ "packages/schema-to-string/src/index.js" ], - "@traversable/schema-to-string/*": ["packages/schema-to-string/src/*.js"], - "@traversable/schema-valibot-adapter": [ "packages/schema-valibot-adapter/src/index.js" ], - "@traversable/schema-valibot-adapter/*": [ "packages/schema-valibot-adapter/src/*.js" ], - "@traversable/schema-zod-adapter": [ "packages/schema-zod-adapter/src/index.js" ], - "@traversable/schema-zod-adapter/*": ["packages/schema-zod-adapter/*.js"] + "@traversable/schema-to-json-schema": [ + "packages/schema-to-json-schema/src/index.js" + ], + "@traversable/schema-to-json-schema/*": [ + "packages/schema-to-json-schema/src/*.js" + ], + "@traversable/schema-to-string": [ + "packages/schema-to-string/src/index.js" + ], + "@traversable/schema-to-string/*": ["packages/schema-to-string/src/*.js"], + "@traversable/schema-valibot-adapter": [ + "packages/schema-valibot-adapter/src/index.js" + ], + "@traversable/schema-valibot-adapter/*": [ + "packages/schema-valibot-adapter/src/*.js" + ], + "@traversable/schema-zod-adapter": [ + "packages/schema-zod-adapter/src/index.js" + ], + "@traversable/schema-zod-adapter/*": ["packages/schema-zod-adapter/*.js"], + "@traversable/schema/*": ["packages/schema/*.js"] } } } diff --git a/tsconfig.build.json b/tsconfig.build.json index d13e1e62..7e6a2ea8 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -13,6 +13,7 @@ { "path": "packages/schema-to-json-schema/tsconfig.build.json" }, { "path": "packages/schema-to-string/tsconfig.build.json" }, { "path": "packages/schema-valibot-adapter/tsconfig.build.json" }, - { "path": "packages/schema-zod-adapter/tsconfig.build.json" } + { "path": "packages/schema-zod-adapter/tsconfig.build.json" }, + { "path": "packages/schema/tsconfig.build.json" } ] } diff --git a/tsconfig.json b/tsconfig.json index a71403c2..8976eff4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,11 +7,12 @@ { "path": "packages/derive-validators" }, { "path": "packages/json" }, { "path": "packages/registry" }, + { "path": "packages/schema" }, + { "path": "packages/schema-core" }, { "path": "packages/schema-generator" }, { "path": "packages/schema-seed" }, { "path": "packages/schema-to-json-schema" }, { "path": "packages/schema-to-string" }, - { "path": "packages/schema-core" }, { "path": "packages/schema-valibot-adapter" }, { "path": "packages/schema-zod-adapter" } ] From 2aff99e1ecd063e74eafa5c86156b37481b5bd9d Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sat, 12 Apr 2025 23:26:23 -0500 Subject: [PATCH 29/45] feat(schema): generating 'equals' extensions --- packages/derive-equals/src/schemas/any.ts | 7 + packages/derive-equals/src/schemas/array.ts | 24 +++ packages/derive-equals/src/schemas/bigint.ts | 7 + packages/derive-equals/src/schemas/boolean.ts | 7 + packages/derive-equals/src/schemas/eq.ts | 8 + packages/derive-equals/src/schemas/integer.ts | 7 + .../derive-equals/src/schemas/intersect.ts | 16 ++ packages/derive-equals/src/schemas/never.ts | 6 + packages/derive-equals/src/schemas/null.ts | 7 + packages/derive-equals/src/schemas/number.ts | 7 + packages/derive-equals/src/schemas/object.ts | 29 ++++ .../derive-equals/src/schemas/optional.ts | 13 ++ packages/derive-equals/src/schemas/record.ts | 32 ++++ packages/derive-equals/src/schemas/string.ts | 6 + packages/derive-equals/src/schemas/symbol.ts | 7 + packages/derive-equals/src/schemas/tuple.ts | 27 +++ .../derive-equals/src/schemas/undefined.ts | 7 + packages/derive-equals/src/schemas/union.ts | 16 ++ packages/derive-equals/src/schemas/unknown.ts | 7 + packages/derive-equals/src/schemas/void.ts | 7 + .../test/test-data/integer/equals.ts | 5 +- .../test/test-data/unknown/equals.ts | 2 +- .../schema/{src/schemas/void.ts => core.ts} | 0 .../src/{schemas => __schemas__}/any.ts | 0 .../src/{schemas => __schemas__}/array.ts | 0 .../src/{schemas => __schemas__}/bigint.ts | 0 .../src/{schemas => __schemas__}/boolean.ts | 0 .../schema/src/{schemas => __schemas__}/eq.ts | 0 .../src/{schemas => __schemas__}/integer.ts | 0 .../src/{schemas => __schemas__}/intersect.ts | 0 .../src/{schemas => __schemas__}/never.ts | 0 .../src/{schemas => __schemas__}/null.ts | 0 .../src/{schemas => __schemas__}/number.ts | 0 .../src/{schemas => __schemas__}/object.ts | 0 .../schema/src/{schemas => __schemas__}/of.ts | 0 .../src/{schemas => __schemas__}/optional.ts | 0 .../src/{schemas => __schemas__}/record.ts | 0 .../src/{schemas => __schemas__}/string.ts | 0 .../src/{schemas => __schemas__}/symbol.ts | 0 .../src/{schemas => __schemas__}/tuple.ts | 0 .../src/{schemas => __schemas__}/undefined.ts | 0 .../src/{schemas => __schemas__}/union.ts | 0 .../src/{schemas => __schemas__}/unknown.ts | 0 packages/schema/src/__schemas__/void.ts | 36 ++++ packages/schema/src/build.ts | 162 +++++++++++++----- packages/schema/src/schemas/any/core.ts | 36 ++++ packages/schema/src/schemas/any/equals.ts | 7 + packages/schema/src/schemas/array/core.ts | 128 ++++++++++++++ packages/schema/src/schemas/array/equals.ts | 24 +++ packages/schema/src/schemas/bigint/core.ts | 99 +++++++++++ packages/schema/src/schemas/bigint/equals.ts | 7 + packages/schema/src/schemas/boolean/core.ts | 37 ++++ packages/schema/src/schemas/boolean/equals.ts | 7 + packages/schema/src/schemas/eq/core.ts | 41 +++++ packages/schema/src/schemas/eq/equals.ts | 8 + packages/schema/src/schemas/integer/core.ts | 100 +++++++++++ packages/schema/src/schemas/integer/equals.ts | 7 + packages/schema/src/schemas/intersect/core.ts | 50 ++++++ .../schema/src/schemas/intersect/equals.ts | 16 ++ packages/schema/src/schemas/never/core.ts | 37 ++++ packages/schema/src/schemas/never/equals.ts | 6 + packages/schema/src/schemas/null/core.ts | 39 +++++ packages/schema/src/schemas/null/equals.ts | 7 + packages/schema/src/schemas/number/core.ts | 139 +++++++++++++++ packages/schema/src/schemas/number/equals.ts | 7 + packages/schema/src/schemas/object/core.ts | 79 +++++++++ packages/schema/src/schemas/object/equals.ts | 29 ++++ packages/schema/src/schemas/of/core.ts | 44 +++++ packages/schema/src/schemas/optional/core.ts | 55 ++++++ .../schema/src/schemas/optional/equals.ts | 13 ++ packages/schema/src/schemas/record/core.ts | 50 ++++++ packages/schema/src/schemas/record/equals.ts | 32 ++++ packages/schema/src/schemas/string/core.ts | 102 +++++++++++ packages/schema/src/schemas/string/equals.ts | 6 + packages/schema/src/schemas/symbol/core.ts | 36 ++++ packages/schema/src/schemas/symbol/equals.ts | 7 + packages/schema/src/schemas/tuple/core.ts | 86 ++++++++++ packages/schema/src/schemas/tuple/equals.ts | 27 +++ packages/schema/src/schemas/undefined/core.ts | 36 ++++ .../schema/src/schemas/undefined/equals.ts | 7 + packages/schema/src/schemas/union/core.ts | 50 ++++++ packages/schema/src/schemas/union/equals.ts | 16 ++ packages/schema/src/schemas/unknown/core.ts | 36 ++++ packages/schema/src/schemas/unknown/equals.ts | 7 + packages/schema/src/schemas/void/core.ts | 36 ++++ packages/schema/src/schemas/void/equals.ts | 7 + 86 files changed, 1969 insertions(+), 46 deletions(-) create mode 100644 packages/derive-equals/src/schemas/any.ts create mode 100644 packages/derive-equals/src/schemas/array.ts create mode 100644 packages/derive-equals/src/schemas/bigint.ts create mode 100644 packages/derive-equals/src/schemas/boolean.ts create mode 100644 packages/derive-equals/src/schemas/eq.ts create mode 100644 packages/derive-equals/src/schemas/integer.ts create mode 100644 packages/derive-equals/src/schemas/intersect.ts create mode 100644 packages/derive-equals/src/schemas/never.ts create mode 100644 packages/derive-equals/src/schemas/null.ts create mode 100644 packages/derive-equals/src/schemas/number.ts create mode 100644 packages/derive-equals/src/schemas/object.ts create mode 100644 packages/derive-equals/src/schemas/optional.ts create mode 100644 packages/derive-equals/src/schemas/record.ts create mode 100644 packages/derive-equals/src/schemas/string.ts create mode 100644 packages/derive-equals/src/schemas/symbol.ts create mode 100644 packages/derive-equals/src/schemas/tuple.ts create mode 100644 packages/derive-equals/src/schemas/undefined.ts create mode 100644 packages/derive-equals/src/schemas/union.ts create mode 100644 packages/derive-equals/src/schemas/unknown.ts create mode 100644 packages/derive-equals/src/schemas/void.ts rename packages/schema/{src/schemas/void.ts => core.ts} (100%) rename packages/schema/src/{schemas => __schemas__}/any.ts (100%) rename packages/schema/src/{schemas => __schemas__}/array.ts (100%) rename packages/schema/src/{schemas => __schemas__}/bigint.ts (100%) rename packages/schema/src/{schemas => __schemas__}/boolean.ts (100%) rename packages/schema/src/{schemas => __schemas__}/eq.ts (100%) rename packages/schema/src/{schemas => __schemas__}/integer.ts (100%) rename packages/schema/src/{schemas => __schemas__}/intersect.ts (100%) rename packages/schema/src/{schemas => __schemas__}/never.ts (100%) rename packages/schema/src/{schemas => __schemas__}/null.ts (100%) rename packages/schema/src/{schemas => __schemas__}/number.ts (100%) rename packages/schema/src/{schemas => __schemas__}/object.ts (100%) rename packages/schema/src/{schemas => __schemas__}/of.ts (100%) rename packages/schema/src/{schemas => __schemas__}/optional.ts (100%) rename packages/schema/src/{schemas => __schemas__}/record.ts (100%) rename packages/schema/src/{schemas => __schemas__}/string.ts (100%) rename packages/schema/src/{schemas => __schemas__}/symbol.ts (100%) rename packages/schema/src/{schemas => __schemas__}/tuple.ts (100%) rename packages/schema/src/{schemas => __schemas__}/undefined.ts (100%) rename packages/schema/src/{schemas => __schemas__}/union.ts (100%) rename packages/schema/src/{schemas => __schemas__}/unknown.ts (100%) create mode 100644 packages/schema/src/__schemas__/void.ts create mode 100644 packages/schema/src/schemas/any/core.ts create mode 100644 packages/schema/src/schemas/any/equals.ts create mode 100644 packages/schema/src/schemas/array/core.ts create mode 100644 packages/schema/src/schemas/array/equals.ts create mode 100644 packages/schema/src/schemas/bigint/core.ts create mode 100644 packages/schema/src/schemas/bigint/equals.ts create mode 100644 packages/schema/src/schemas/boolean/core.ts create mode 100644 packages/schema/src/schemas/boolean/equals.ts create mode 100644 packages/schema/src/schemas/eq/core.ts create mode 100644 packages/schema/src/schemas/eq/equals.ts create mode 100644 packages/schema/src/schemas/integer/core.ts create mode 100644 packages/schema/src/schemas/integer/equals.ts create mode 100644 packages/schema/src/schemas/intersect/core.ts create mode 100644 packages/schema/src/schemas/intersect/equals.ts create mode 100644 packages/schema/src/schemas/never/core.ts create mode 100644 packages/schema/src/schemas/never/equals.ts create mode 100644 packages/schema/src/schemas/null/core.ts create mode 100644 packages/schema/src/schemas/null/equals.ts create mode 100644 packages/schema/src/schemas/number/core.ts create mode 100644 packages/schema/src/schemas/number/equals.ts create mode 100644 packages/schema/src/schemas/object/core.ts create mode 100644 packages/schema/src/schemas/object/equals.ts create mode 100644 packages/schema/src/schemas/of/core.ts create mode 100644 packages/schema/src/schemas/optional/core.ts create mode 100644 packages/schema/src/schemas/optional/equals.ts create mode 100644 packages/schema/src/schemas/record/core.ts create mode 100644 packages/schema/src/schemas/record/equals.ts create mode 100644 packages/schema/src/schemas/string/core.ts create mode 100644 packages/schema/src/schemas/string/equals.ts create mode 100644 packages/schema/src/schemas/symbol/core.ts create mode 100644 packages/schema/src/schemas/symbol/equals.ts create mode 100644 packages/schema/src/schemas/tuple/core.ts create mode 100644 packages/schema/src/schemas/tuple/equals.ts create mode 100644 packages/schema/src/schemas/undefined/core.ts create mode 100644 packages/schema/src/schemas/undefined/equals.ts create mode 100644 packages/schema/src/schemas/union/core.ts create mode 100644 packages/schema/src/schemas/union/equals.ts create mode 100644 packages/schema/src/schemas/unknown/core.ts create mode 100644 packages/schema/src/schemas/unknown/equals.ts create mode 100644 packages/schema/src/schemas/void/core.ts create mode 100644 packages/schema/src/schemas/void/equals.ts diff --git a/packages/derive-equals/src/schemas/any.ts b/packages/derive-equals/src/schemas/any.ts new file mode 100644 index 00000000..09d8da5f --- /dev/null +++ b/packages/derive-equals/src/schemas/any.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: unknown, right: unknown): boolean { + return Object_is(left, right) +} diff --git a/packages/derive-equals/src/schemas/array.ts b/packages/derive-equals/src/schemas/array.ts new file mode 100644 index 00000000..aa06a7bf --- /dev/null +++ b/packages/derive-equals/src/schemas/array.ts @@ -0,0 +1,24 @@ +import type { Equal } from '@traversable/registry' +import { has, Array_isArray, Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal + +export function equals(arraySchema: t.array): equals +export function equals(arraySchema: t.array): equals +export function equals({ def }: t.array<{ equals: Equal }>): Equal { + let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is + function arrayEquals(l: unknown[], r: unknown[]): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + let len = l.length + if (len !== r.length) return false + for (let ix = len; ix-- !== 0;) + if (!equals(l[ix], r[ix])) return false + return true + } else return false + } + return arrayEquals +} + diff --git a/packages/derive-equals/src/schemas/bigint.ts b/packages/derive-equals/src/schemas/bigint.ts new file mode 100644 index 00000000..3f38a8a5 --- /dev/null +++ b/packages/derive-equals/src/schemas/bigint.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: bigint, right: bigint): boolean { + return Object_is(left, right) +} diff --git a/packages/derive-equals/src/schemas/boolean.ts b/packages/derive-equals/src/schemas/boolean.ts new file mode 100644 index 00000000..c060588a --- /dev/null +++ b/packages/derive-equals/src/schemas/boolean.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: boolean, right: boolean): boolean { + return Object_is(left, right) +} diff --git a/packages/derive-equals/src/schemas/eq.ts b/packages/derive-equals/src/schemas/eq.ts new file mode 100644 index 00000000..9df41231 --- /dev/null +++ b/packages/derive-equals/src/schemas/eq.ts @@ -0,0 +1,8 @@ +import type { Equal } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +export type equals = never | Equal +export function equals(eqSchema: t.eq): equals +export function equals(): Equal { + return (left: unknown, right: unknown) => t.eq(left)(right) +} diff --git a/packages/derive-equals/src/schemas/integer.ts b/packages/derive-equals/src/schemas/integer.ts new file mode 100644 index 00000000..a599f588 --- /dev/null +++ b/packages/derive-equals/src/schemas/integer.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { SameValueNumber } from "@traversable/registry" + +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} diff --git a/packages/derive-equals/src/schemas/intersect.ts b/packages/derive-equals/src/schemas/intersect.ts new file mode 100644 index 00000000..ce880aa1 --- /dev/null +++ b/packages/derive-equals/src/schemas/intersect.ts @@ -0,0 +1,16 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = Equal +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals({ def }: t.intersect<{ equals: Equal }[]>): Equal { + function intersectEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (!def[ix].equals(l, r)) return false + return true + } + return intersectEquals +} diff --git a/packages/derive-equals/src/schemas/never.ts b/packages/derive-equals/src/schemas/never.ts new file mode 100644 index 00000000..7348de1a --- /dev/null +++ b/packages/derive-equals/src/schemas/never.ts @@ -0,0 +1,6 @@ +import type { Equal } from "@traversable/registry" + +export type equals = Equal +export function equals(left: never, right: never): boolean { + return false +} diff --git a/packages/derive-equals/src/schemas/null.ts b/packages/derive-equals/src/schemas/null.ts new file mode 100644 index 00000000..fe92c107 --- /dev/null +++ b/packages/derive-equals/src/schemas/null.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: null, right: null): boolean { + return Object_is(left, right) +} diff --git a/packages/derive-equals/src/schemas/number.ts b/packages/derive-equals/src/schemas/number.ts new file mode 100644 index 00000000..a599f588 --- /dev/null +++ b/packages/derive-equals/src/schemas/number.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { SameValueNumber } from "@traversable/registry" + +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} diff --git a/packages/derive-equals/src/schemas/object.ts b/packages/derive-equals/src/schemas/object.ts new file mode 100644 index 00000000..3cd8134f --- /dev/null +++ b/packages/derive-equals/src/schemas/object.ts @@ -0,0 +1,29 @@ +import type * as T from '@traversable/registry' +import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | T.Equal +export function equals(objectSchema: t.object): equals> +export function equals(objectSchema: t.object): equals> +export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { + function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + for (const k in def) { + const lHas = Object_hasOwn(l, k) + const rHas = Object_hasOwn(r, k) + if (lHas) { + if (!rHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (rHas) { + if (!lHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (!def[k].equals(l[k], r[k])) return false + } + return true + } + return objectEquals +} diff --git a/packages/derive-equals/src/schemas/optional.ts b/packages/derive-equals/src/schemas/optional.ts new file mode 100644 index 00000000..25944376 --- /dev/null +++ b/packages/derive-equals/src/schemas/optional.ts @@ -0,0 +1,13 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal +export function equals(optionalSchema: t.optional): equals +export function equals(optionalSchema: t.optional): equals +export function equals({ def }: t.optional<{ equals: Equal }>): Equal { + return function optionalEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + return def.equals(l, r) + } +} diff --git a/packages/derive-equals/src/schemas/record.ts b/packages/derive-equals/src/schemas/record.ts new file mode 100644 index 00000000..07addff1 --- /dev/null +++ b/packages/derive-equals/src/schemas/record.ts @@ -0,0 +1,32 @@ +import type { Equal } from '@traversable/registry' +import { Array_isArray, Object_is, Object_keys, Object_hasOwn } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal +export function equals(recordSchema: t.record): equals +export function equals(recordSchema: t.record): equals +export function equals({ def }: t.record<{ equals: Equal }>): Equal> { + function recordEquals(l: Record, r: Record): boolean { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + const lhs = Object_keys(l) + const rhs = Object_keys(r) + let len = lhs.length + let k: string + if (len !== rhs.length) return false + for (let ix = len; ix-- !== 0;) { + k = lhs[ix] + if (!Object_hasOwn(r, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + len = rhs.length + for (let ix = len; ix-- !== 0;) { + k = rhs[ix] + if (!Object_hasOwn(l, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + return true + } + return recordEquals +} diff --git a/packages/derive-equals/src/schemas/string.ts b/packages/derive-equals/src/schemas/string.ts new file mode 100644 index 00000000..b9444108 --- /dev/null +++ b/packages/derive-equals/src/schemas/string.ts @@ -0,0 +1,6 @@ +import type { Equal } from '@traversable/registry' + +export type equals = Equal +export function equals(left: string, right: string): boolean { + return left === right +} diff --git a/packages/derive-equals/src/schemas/symbol.ts b/packages/derive-equals/src/schemas/symbol.ts new file mode 100644 index 00000000..24e82beb --- /dev/null +++ b/packages/derive-equals/src/schemas/symbol.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: symbol, right: symbol): boolean { + return Object_is(left, right) +} diff --git a/packages/derive-equals/src/schemas/tuple.ts b/packages/derive-equals/src/schemas/tuple.ts new file mode 100644 index 00000000..4d9de15c --- /dev/null +++ b/packages/derive-equals/src/schemas/tuple.ts @@ -0,0 +1,27 @@ +import type { Equal } from '@traversable/registry' +import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +export type equals = Equal + +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple) { + function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + for (let ix = tupleSchema.def.length; ix-- !== 0;) { + if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue + if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false + if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false + if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { + if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false + } + } + return true + } + return false + } + return tupleEquals +} diff --git a/packages/derive-equals/src/schemas/undefined.ts b/packages/derive-equals/src/schemas/undefined.ts new file mode 100644 index 00000000..2836cd51 --- /dev/null +++ b/packages/derive-equals/src/schemas/undefined.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: undefined, right: undefined): boolean { + return Object_is(left, right) +} diff --git a/packages/derive-equals/src/schemas/union.ts b/packages/derive-equals/src/schemas/union.ts new file mode 100644 index 00000000..c0275e69 --- /dev/null +++ b/packages/derive-equals/src/schemas/union.ts @@ -0,0 +1,16 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = Equal +export function equals(unionSchema: t.union<[...S]>): equals +export function equals(unionSchema: t.union<[...S]>): equals +export function equals({ def }: t.union<{ equals: Equal }[]>): Equal { + function unionEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (def[ix].equals(l, r)) return true + return false + } + return unionEquals +} diff --git a/packages/derive-equals/src/schemas/unknown.ts b/packages/derive-equals/src/schemas/unknown.ts new file mode 100644 index 00000000..41b30fd5 --- /dev/null +++ b/packages/derive-equals/src/schemas/unknown.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: any, right: any): boolean { + return Object_is(left, right) +} diff --git a/packages/derive-equals/src/schemas/void.ts b/packages/derive-equals/src/schemas/void.ts new file mode 100644 index 00000000..6f7b9779 --- /dev/null +++ b/packages/derive-equals/src/schemas/void.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: void, right: void): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/test/test-data/integer/equals.ts b/packages/schema-generator/test/test-data/integer/equals.ts index 15704197..a599f588 100644 --- a/packages/schema-generator/test/test-data/integer/equals.ts +++ b/packages/schema-generator/test/test-data/integer/equals.ts @@ -1,6 +1,7 @@ -import { Equal } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { SameValueNumber } from "@traversable/registry" export type equals = Equal export function equals(left: number, right: number): boolean { - return Equal.SameValueNumber(left, right) + return SameValueNumber(left, right) } diff --git a/packages/schema-generator/test/test-data/unknown/equals.ts b/packages/schema-generator/test/test-data/unknown/equals.ts index 385f31f4..41b30fd5 100644 --- a/packages/schema-generator/test/test-data/unknown/equals.ts +++ b/packages/schema-generator/test/test-data/unknown/equals.ts @@ -4,4 +4,4 @@ import { Object_is } from "@traversable/registry" export type equals = Equal export function equals(left: any, right: any): boolean { return Object_is(left, right) -} \ No newline at end of file +} diff --git a/packages/schema/src/schemas/void.ts b/packages/schema/core.ts similarity index 100% rename from packages/schema/src/schemas/void.ts rename to packages/schema/core.ts diff --git a/packages/schema/src/schemas/any.ts b/packages/schema/src/__schemas__/any.ts similarity index 100% rename from packages/schema/src/schemas/any.ts rename to packages/schema/src/__schemas__/any.ts diff --git a/packages/schema/src/schemas/array.ts b/packages/schema/src/__schemas__/array.ts similarity index 100% rename from packages/schema/src/schemas/array.ts rename to packages/schema/src/__schemas__/array.ts diff --git a/packages/schema/src/schemas/bigint.ts b/packages/schema/src/__schemas__/bigint.ts similarity index 100% rename from packages/schema/src/schemas/bigint.ts rename to packages/schema/src/__schemas__/bigint.ts diff --git a/packages/schema/src/schemas/boolean.ts b/packages/schema/src/__schemas__/boolean.ts similarity index 100% rename from packages/schema/src/schemas/boolean.ts rename to packages/schema/src/__schemas__/boolean.ts diff --git a/packages/schema/src/schemas/eq.ts b/packages/schema/src/__schemas__/eq.ts similarity index 100% rename from packages/schema/src/schemas/eq.ts rename to packages/schema/src/__schemas__/eq.ts diff --git a/packages/schema/src/schemas/integer.ts b/packages/schema/src/__schemas__/integer.ts similarity index 100% rename from packages/schema/src/schemas/integer.ts rename to packages/schema/src/__schemas__/integer.ts diff --git a/packages/schema/src/schemas/intersect.ts b/packages/schema/src/__schemas__/intersect.ts similarity index 100% rename from packages/schema/src/schemas/intersect.ts rename to packages/schema/src/__schemas__/intersect.ts diff --git a/packages/schema/src/schemas/never.ts b/packages/schema/src/__schemas__/never.ts similarity index 100% rename from packages/schema/src/schemas/never.ts rename to packages/schema/src/__schemas__/never.ts diff --git a/packages/schema/src/schemas/null.ts b/packages/schema/src/__schemas__/null.ts similarity index 100% rename from packages/schema/src/schemas/null.ts rename to packages/schema/src/__schemas__/null.ts diff --git a/packages/schema/src/schemas/number.ts b/packages/schema/src/__schemas__/number.ts similarity index 100% rename from packages/schema/src/schemas/number.ts rename to packages/schema/src/__schemas__/number.ts diff --git a/packages/schema/src/schemas/object.ts b/packages/schema/src/__schemas__/object.ts similarity index 100% rename from packages/schema/src/schemas/object.ts rename to packages/schema/src/__schemas__/object.ts diff --git a/packages/schema/src/schemas/of.ts b/packages/schema/src/__schemas__/of.ts similarity index 100% rename from packages/schema/src/schemas/of.ts rename to packages/schema/src/__schemas__/of.ts diff --git a/packages/schema/src/schemas/optional.ts b/packages/schema/src/__schemas__/optional.ts similarity index 100% rename from packages/schema/src/schemas/optional.ts rename to packages/schema/src/__schemas__/optional.ts diff --git a/packages/schema/src/schemas/record.ts b/packages/schema/src/__schemas__/record.ts similarity index 100% rename from packages/schema/src/schemas/record.ts rename to packages/schema/src/__schemas__/record.ts diff --git a/packages/schema/src/schemas/string.ts b/packages/schema/src/__schemas__/string.ts similarity index 100% rename from packages/schema/src/schemas/string.ts rename to packages/schema/src/__schemas__/string.ts diff --git a/packages/schema/src/schemas/symbol.ts b/packages/schema/src/__schemas__/symbol.ts similarity index 100% rename from packages/schema/src/schemas/symbol.ts rename to packages/schema/src/__schemas__/symbol.ts diff --git a/packages/schema/src/schemas/tuple.ts b/packages/schema/src/__schemas__/tuple.ts similarity index 100% rename from packages/schema/src/schemas/tuple.ts rename to packages/schema/src/__schemas__/tuple.ts diff --git a/packages/schema/src/schemas/undefined.ts b/packages/schema/src/__schemas__/undefined.ts similarity index 100% rename from packages/schema/src/schemas/undefined.ts rename to packages/schema/src/__schemas__/undefined.ts diff --git a/packages/schema/src/schemas/union.ts b/packages/schema/src/__schemas__/union.ts similarity index 100% rename from packages/schema/src/schemas/union.ts rename to packages/schema/src/__schemas__/union.ts diff --git a/packages/schema/src/schemas/unknown.ts b/packages/schema/src/__schemas__/unknown.ts similarity index 100% rename from packages/schema/src/schemas/unknown.ts rename to packages/schema/src/__schemas__/unknown.ts diff --git a/packages/schema/src/__schemas__/void.ts b/packages/schema/src/__schemas__/void.ts new file mode 100644 index 00000000..d178213e --- /dev/null +++ b/packages/schema/src/__schemas__/void.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { void_ as void, void_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface void_ extends void_.core { + //<%= Types %> +} + +function VoidSchema(src: unknown): src is void { return src === void 0 } +VoidSchema.tag = URI.void +VoidSchema.def = void 0 as void + +const void_ = Object_assign( + VoidSchema, + userDefinitions, +) as void_ + +Object_assign(void_, userExtensions) + +declare namespace void_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.void + _type: void + get def(): this['_type'] + } +} diff --git a/packages/schema/src/build.ts b/packages/schema/src/build.ts index 0d8e0949..53141e8d 100755 --- a/packages/schema/src/build.ts +++ b/packages/schema/src/build.ts @@ -3,8 +3,6 @@ import * as path from 'node:path' import * as fs from 'node:fs' import { fn } from '@traversable/registry' -let LIBS = path.join(path.resolve(), 'node_modules', '@traversable') - let SCHEMA_WHITELIST = [ 'of', 'eq', @@ -31,79 +29,157 @@ let SCHEMA_WHITELIST = [ 'object', ] +let FILE_BLACKLIST = [ + 'README.md', +] as const satisfies any[] + let LIB_BLACKLIST = [ 'registry', -] as const satisfies any[] +] satisfies any[] let LIB_WHITELIST = [ - 'schema-core', -] as const satisfies any[] - -let FILE_BLACKLIST = [ - 'README.md', -] as const satisfies any[] + 'derive-equals', + // 'schema-to-json-schema', +] satisfies any[] -let SCHEMAS_DIR +let LIB_NAME_TO_FILENAME = { + 'derive-equals': 'equals.ts', + 'schema-to-json-schema': 'toJsonSchema.ts', +} let PATH = { sourcesDir: path.join(path.resolve(), 'node_modules', '@traversable'), target: path.join(path.resolve(), 'src', 'schemas'), } -// FROM: -// import type { Guarded, Schema, SchemaLike } from '../namespace.js' - -// TO: -// import type { Guarded, Schema, SchemaLike } from '@traversable/schema-core/namespace' - let RelativeImport = { - from: /(\.\.\/)namespace.js/g, - to: '@traversable/schema-core/namespace' + namespace: { + /** + * @example + * import { stuff } from '../namespace.js' + */ + from: /(\.\.\/)namespace.js/g, + to: '@traversable/schema-core/namespace' + }, + local: { + /** + * @example + * import type { of } from './of.js' + */ + from: /\.\/([^]*?)\.js/g, + /** + * @example + * import type { of } from '../of/core.js' + */ + to: (mod: string) => `.${mod.slice(0, -'.js'.length)}/core.js`, + }, } -console.log(`'../namespace.js'`.replace(RelativeImport.from, RelativeImport.to)) - -function buildSchemas() { - if (!fs.existsSync(PATH.target)) { fs.mkdirSync(PATH.target) } +let isKeyOf = (k: keyof any, t: T): k is keyof T => + !!t && (typeof t === 'object' || typeof t === 'function') && k in t +function buildCoreSchemas() { + if (!fs.existsSync(PATH.target)) { + fs.mkdirSync(PATH.target) + } return fs.readdirSync(PATH.sourcesDir, { withFileTypes: true }) - .filter(({ name }) => !LIB_BLACKLIST.includes(name as never) && LIB_WHITELIST.includes(name as never)) + .filter(({ name }) => name === 'schema-core') .map( ({ name, parentPath }) => fn.pipe( - path.join(parentPath, name, 'src', 'schemas'), + path.join( + parentPath, + name, + 'src', + 'schemas' + ), (absolutePath) => fs.readdirSync(absolutePath, { withFileTypes: true }), + (schemaPaths) => { + schemaPaths.forEach((schemaPath) => { + let dirName = schemaPath.name.endsWith('.ts') + ? schemaPath.name.slice(0, -'.ts'.length) + : schemaPath.name + let targetDirPath = path.join( + PATH.target, + dirName, + ) + if (!fs.existsSync(targetDirPath)) { + fs.mkdirSync(targetDirPath) + } + }) + return schemaPaths + }, (schemaPaths) => schemaPaths.map(({ name, parentPath }) => [ - // name.endsWith('.ts') ? name.slice(0, -'.ts'.length) : name, - path.join(PATH.target, name), - fs.readFileSync(path.join(parentPath, name)).toString('utf8') + path.join( + PATH.target, + name.slice(0, -'.ts'.length), + 'core.ts', + ), + fs.readFileSync( + path.join( + parentPath, + name, + ) + ).toString('utf8') ] satisfies [any, any]), fn.map( fn.flow( - ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.from, RelativeImport.to)] satisfies [any, any], - ([filePath, content]) => fs.writeFileSync(filePath, content)), + ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], + ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local.from, RelativeImport.local.to)] satisfies [any, any], + ([filePath, content]) => fs.writeFileSync(filePath, content) + ) ) - - // xs => Object.fromEntries(xs), - - // (dirPaths) => dirPaths.filter((dirPath) => dirPath.endsWith('.ts')), - // (path, { withFileTypes: true }), - // fn.map(({ name, parentPath, isFile }) => name + parentPath) - ) ) +} - - - +function buildSchemaExtensions() { + return fs.readdirSync(PATH.sourcesDir, { withFileTypes: true }) + .filter(({ name }) => !LIB_BLACKLIST.includes(name) && LIB_WHITELIST.includes(name)) + .map( + ({ name, parentPath }) => fn.pipe( + path.join( + parentPath, + name, + 'src', + 'schemas', + ), + (absolutePath) => fs.readdirSync(absolutePath, { withFileTypes: true }), + (schemaPaths) => schemaPaths.map( + ({ name: schemaName, parentPath }) => [ + path.join( + PATH.target, + schemaName.slice(0, -'.ts'.length), + isKeyOf(name, LIB_NAME_TO_FILENAME) ? LIB_NAME_TO_FILENAME[name] : 'BORKED', + ), + fs.readFileSync( + path.join( + parentPath, + schemaName, + ) + ).toString('utf8') + ] satisfies [any, any] + ), + fn.map( + fn.flow( + ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], + ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local.from, RelativeImport.local.to)] satisfies [any, any], + ([filePath, content]) => fs.writeFileSync(filePath, content)), + ), + ) + ) } -console.log('OUT', buildSchemas()) +function buildSchemas() { + buildCoreSchemas() + buildSchemaExtensions() +} +buildSchemas() /** * ## TODO * - * - [ ] Pull the .ts files out of `@traversable/schema-core` + * - [x] Pull the .ts files out of `@traversable/schema-core` + * - [x] Pull the .ts files out of `@traversable/derive-equals` + * - [ ] Pull the .ts files out of `@traversable/schema-to-json-schema` */ - -export const TEST: 1 = 1 diff --git a/packages/schema/src/schemas/any/core.ts b/packages/schema/src/schemas/any/core.ts new file mode 100644 index 00000000..877f92af --- /dev/null +++ b/packages/schema/src/schemas/any/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { any_ as any } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface any_ extends any_.core { + //<%= Types %> +} + +function AnySchema(src: unknown): src is any { return true } +AnySchema.tag = URI.any +AnySchema.def = void 0 as any + +const any_ = Object_assign( + AnySchema, + userDefinitions, +) as any_ + +Object_assign(any_, userExtensions) + +declare namespace any_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.any + _type: any + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/any/equals.ts b/packages/schema/src/schemas/any/equals.ts new file mode 100644 index 00000000..09d8da5f --- /dev/null +++ b/packages/schema/src/schemas/any/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: unknown, right: unknown): boolean { + return Object_is(left, right) +} diff --git a/packages/schema/src/schemas/array/core.ts b/packages/schema/src/schemas/array/core.ts new file mode 100644 index 00000000..77624f60 --- /dev/null +++ b/packages/schema/src/schemas/array/core.ts @@ -0,0 +1,128 @@ +import type { + Bounds, + Integer, + Unknown, +} from '@traversable/registry' +import { + Array_isArray, + array as arrayOf, + bindUserExtensions, + carryover, + within, + _isPredicate, + has, + Math_max, + Math_min, + Number_isSafeInteger, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Guarded, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +import type { of } from '../of/core.js' + +/** @internal */ +function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { + return Object_assign(function BoundedArraySchema(u: unknown) { + return Array_isArray(u) && within(bounds)(u.length) + }, carry, array(schema)) +} + +export interface array extends array.core { + //<%= Types %> +} + +export function array(schema: S, readonly: 'readonly'): readonlyArray +export function array(schema: S): array +export function array(schema: S): array>> +export function array(schema: S): array { + return array.def(schema) +} + +export namespace array { + export let userDefinitions: Record = { + //<%= Definitions %> + } as array + export function def(x: S, prev?: array): array + export function def(x: S, prev?: unknown): array + export function def(x: S, prev?: array): array + /* v8 ignore next 1 */ + export function def(x: unknown, prev?: unknown): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray + function ArraySchema(src: unknown) { return predicate(src) } + ArraySchema.tag = URI.array + ArraySchema.def = x + ArraySchema.min = function arrayMin(minLength: Min) { + return Object_assign( + boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), + { minLength }, + ) + } + ArraySchema.max = function arrayMax(maxLength: Max) { + return Object_assign( + boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), + { maxLength }, + ) + } + ArraySchema.between = function arrayBetween( + min: Min, + max: Max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max) + ) { + return Object_assign( + boundedArray(x, { gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) + } + if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength + if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength + Object_assign(ArraySchema, userDefinitions) + return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) + } +} + +export declare namespace array { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.array + get def(): S + _type: S['_type' & keyof S][] + minLength?: number + maxLength?: number + min>(minLength: Min): array.Min + max>(maxLength: Max): array.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> + } + type Min + = [Self] extends [{ maxLength: number }] + ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> + : array.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> + : array.max + ; + interface min extends array { minLength: Min } + interface max extends array { maxLength: Max } + interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } + type type = never | T +} + +export const readonlyArray: { + (schema: S): readonlyArray + (schema: S): readonlyArray> +} = array +export interface readonlyArray { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: ReadonlyArray +} diff --git a/packages/schema/src/schemas/array/equals.ts b/packages/schema/src/schemas/array/equals.ts new file mode 100644 index 00000000..aa06a7bf --- /dev/null +++ b/packages/schema/src/schemas/array/equals.ts @@ -0,0 +1,24 @@ +import type { Equal } from '@traversable/registry' +import { has, Array_isArray, Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal + +export function equals(arraySchema: t.array): equals +export function equals(arraySchema: t.array): equals +export function equals({ def }: t.array<{ equals: Equal }>): Equal { + let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is + function arrayEquals(l: unknown[], r: unknown[]): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + let len = l.length + if (len !== r.length) return false + for (let ix = len; ix-- !== 0;) + if (!equals(l[ix], r[ix])) return false + return true + } else return false + } + return arrayEquals +} + diff --git a/packages/schema/src/schemas/bigint/core.ts b/packages/schema/src/schemas/bigint/core.ts new file mode 100644 index 00000000..f7d55261 --- /dev/null +++ b/packages/schema/src/schemas/bigint/core.ts @@ -0,0 +1,99 @@ +import type { Bounds, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Object_assign, + URI, + withinBig as within, +} from '@traversable/registry' + +export { bigint_ as bigint } + +/** @internal */ +function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedBigIntSchema(u: unknown) { + return bigint_(u) && within(bounds)(u) + }, carry, bigint_) +} + +interface bigint_ extends bigint_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function BigIntSchema(src: unknown) { return typeof src === 'bigint' } +BigIntSchema.tag = URI.bigint +BigIntSchema.def = 0n + +const bigint_ = Object_assign( + BigIntSchema, + userDefinitions, +) as bigint_ + +bigint_.min = function bigIntMin(minimum) { + return Object_assign( + boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +bigint_.max = function bigIntMax(maximum) { + return Object_assign( + boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +bigint_.between = function bigIntBetween( + min, + max, + minimum = (max < min ? max : min), + maximum = (max < min ? min : max), +) { + return Object_assign( + boundedBigInt({ gte: minimum, lte: maximum }), + { minimum, maximum } + ) +} + +Object_assign( + bigint_, + bindUserExtensions(bigint_, userExtensions), +) + +declare namespace bigint_ { + interface core extends bigint_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: bigint + tag: URI.bigint + get def(): this['_type'] + minimum?: bigint + maximum?: bigint + } + type Min + = [Self] extends [{ maximum: bigint }] + ? bigint_.between<[min: X, max: Self['maximum']]> + : bigint_.min + ; + type Max + = [Self] extends [{ minimum: bigint }] + ? bigint_.between<[min: Self['minimum'], max: X]> + : bigint_.max + ; + interface methods { + min(minimum: Min): bigint_.Min + max(maximum: Max): bigint_.Max + between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> + } + interface min extends bigint_ { minimum: Min } + interface max extends bigint_ { maximum: Max } + interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } +} + diff --git a/packages/schema/src/schemas/bigint/equals.ts b/packages/schema/src/schemas/bigint/equals.ts new file mode 100644 index 00000000..3f38a8a5 --- /dev/null +++ b/packages/schema/src/schemas/bigint/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: bigint, right: bigint): boolean { + return Object_is(left, right) +} diff --git a/packages/schema/src/schemas/boolean/core.ts b/packages/schema/src/schemas/boolean/core.ts new file mode 100644 index 00000000..c89adf4b --- /dev/null +++ b/packages/schema/src/schemas/boolean/core.ts @@ -0,0 +1,37 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { boolean_ as boolean } + +interface boolean_ extends boolean_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } + +BooleanSchema.tag = URI.boolean +BooleanSchema.def = false + +const boolean_ = Object_assign( + BooleanSchema, + userDefinitions, +) as boolean_ + +Object_assign(boolean_, userExtensions) + +declare namespace boolean_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.boolean + _type: boolean + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/boolean/equals.ts b/packages/schema/src/schemas/boolean/equals.ts new file mode 100644 index 00000000..c060588a --- /dev/null +++ b/packages/schema/src/schemas/boolean/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: boolean, right: boolean): boolean { + return Object_is(left, right) +} diff --git a/packages/schema/src/schemas/eq/core.ts b/packages/schema/src/schemas/eq/core.ts new file mode 100644 index 00000000..d701a0f6 --- /dev/null +++ b/packages/schema/src/schemas/eq/core.ts @@ -0,0 +1,41 @@ +import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' +import { applyOptions, bindUserExtensions, _isPredicate, Object_assign, URI } from '@traversable/registry' + +export function eq>(value: V, options?: Options): eq> +export function eq(value: V, options?: Options): eq +export function eq(value: V, options?: Options): eq { + return eq.def(value, options) +} + +export interface eq extends eq.core { + //<%= Types %> +} + +export namespace eq { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(value: T, options?: Options): eq + /* v8 ignore next 1 */ + export function def(x: T, $?: Options): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const options = applyOptions($) + const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return predicate(src) } + EqSchema.tag = URI.eq + EqSchema.def = x + Object_assign(EqSchema, eq.userDefinitions) + return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) + } +} + +export declare namespace eq { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.eq + _type: V + get def(): V + } +} diff --git a/packages/schema/src/schemas/eq/equals.ts b/packages/schema/src/schemas/eq/equals.ts new file mode 100644 index 00000000..9df41231 --- /dev/null +++ b/packages/schema/src/schemas/eq/equals.ts @@ -0,0 +1,8 @@ +import type { Equal } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +export type equals = never | Equal +export function equals(eqSchema: t.eq): equals +export function equals(): Equal { + return (left: unknown, right: unknown) => t.eq(left)(right) +} diff --git a/packages/schema/src/schemas/integer/core.ts b/packages/schema/src/schemas/integer/core.ts new file mode 100644 index 00000000..2969e5a1 --- /dev/null +++ b/packages/schema/src/schemas/integer/core.ts @@ -0,0 +1,100 @@ +import type { Bounds, Integer, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Number_isSafeInteger, + Object_assign, + URI, + within, +} from '@traversable/registry' + + +export { integer } + +/** @internal */ +function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedIntegerSchema(u: unknown) { + return integer(u) && within(bounds)(u) + }, carry, integer) +} + +interface integer extends integer.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } +IntegerSchema.tag = URI.integer +IntegerSchema.def = 0 + +const integer = Object_assign( + IntegerSchema, + userDefinitions, +) as integer + +integer.min = function integerMin(minimum) { + return Object_assign( + boundedInteger({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +integer.max = function integerMax(maximum) { + return Object_assign( + boundedInteger({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +integer.between = function integerBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedInteger({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + integer, + bindUserExtensions(integer, userExtensions), +) + +declare namespace integer { + interface core extends integer.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.integer + get def(): this['_type'] + minimum?: number + maximum?: number + } + interface methods { + min>(minimum: Min): integer.Min + max>(maximum: Max): integer.Max + between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maximum: number }] + ? integer.between<[min: X, max: Self['maximum']]> + : integer.min + type Max + = [Self] extends [{ minimum: number }] + ? integer.between<[min: Self['minimum'], max: X]> + : integer.max + interface min extends integer { minimum: Min } + interface max extends integer { maximum: Max } + interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } +} diff --git a/packages/schema/src/schemas/integer/equals.ts b/packages/schema/src/schemas/integer/equals.ts new file mode 100644 index 00000000..a599f588 --- /dev/null +++ b/packages/schema/src/schemas/integer/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { SameValueNumber } from "@traversable/registry" + +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} diff --git a/packages/schema/src/schemas/intersect/core.ts b/packages/schema/src/schemas/intersect/core.ts new file mode 100644 index 00000000..916aa265 --- /dev/null +++ b/packages/schema/src/schemas/intersect/core.ts @@ -0,0 +1,50 @@ +import type { Unknown } from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + intersect as intersect$, + isUnknown as isAny, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Entry, IntersectType, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: readonly unknown[]) { + return intersect.def(schemas) +} + +export interface intersect extends intersect.core { + //<%= Types %> +} + +export namespace intersect { + export let userDefinitions: Record = { + //<%= Definitions %> + } as intersect + export function def(xs: readonly [...T]): intersect + /* v8 ignore next 1 */ + export function def(xs: readonly unknown[]): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny + function IntersectSchema(src: unknown) { return predicate(src) } + IntersectSchema.tag = URI.intersect + IntersectSchema.def = xs + Object_assign(IntersectSchema, intersect.userDefinitions) + return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) + } +} + +export declare namespace intersect { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.intersect + get def(): S + _type: IntersectType + } + type type> = never | T +} diff --git a/packages/schema/src/schemas/intersect/equals.ts b/packages/schema/src/schemas/intersect/equals.ts new file mode 100644 index 00000000..ce880aa1 --- /dev/null +++ b/packages/schema/src/schemas/intersect/equals.ts @@ -0,0 +1,16 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = Equal +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals({ def }: t.intersect<{ equals: Equal }[]>): Equal { + function intersectEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (!def[ix].equals(l, r)) return false + return true + } + return intersectEquals +} diff --git a/packages/schema/src/schemas/never/core.ts b/packages/schema/src/schemas/never/core.ts new file mode 100644 index 00000000..a0077281 --- /dev/null +++ b/packages/schema/src/schemas/never/core.ts @@ -0,0 +1,37 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { never_ as never } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface never_ extends never_.core { + //<%= Types %> +} + +function NeverSchema(src: unknown): src is never { return false } +NeverSchema.tag = URI.never; +NeverSchema.def = void 0 as never + +const never_ = Object_assign( + NeverSchema, + userDefinitions, +) as never_ + +Object_assign(never_, userExtensions) + +export declare namespace never_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.never + _type: never + get def(): this['_type'] + } +} + diff --git a/packages/schema/src/schemas/never/equals.ts b/packages/schema/src/schemas/never/equals.ts new file mode 100644 index 00000000..7348de1a --- /dev/null +++ b/packages/schema/src/schemas/never/equals.ts @@ -0,0 +1,6 @@ +import type { Equal } from "@traversable/registry" + +export type equals = Equal +export function equals(left: never, right: never): boolean { + return false +} diff --git a/packages/schema/src/schemas/null/core.ts b/packages/schema/src/schemas/null/core.ts new file mode 100644 index 00000000..befebeb5 --- /dev/null +++ b/packages/schema/src/schemas/null/core.ts @@ -0,0 +1,39 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { null_ as null, null_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface null_ extends null_.core { + //<%= Types %> +} + +function NullSchema(src: unknown): src is null { return src === null } +NullSchema.def = null +NullSchema.tag = URI.null + +const null_ = Object_assign( + NullSchema, + userDefinitions, +) as null_ + +Object_assign( + null_, + userExtensions, +) + +declare namespace null_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.null + _type: null + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/null/equals.ts b/packages/schema/src/schemas/null/equals.ts new file mode 100644 index 00000000..fe92c107 --- /dev/null +++ b/packages/schema/src/schemas/null/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: null, right: null): boolean { + return Object_is(left, right) +} diff --git a/packages/schema/src/schemas/number/core.ts b/packages/schema/src/schemas/number/core.ts new file mode 100644 index 00000000..5972ae85 --- /dev/null +++ b/packages/schema/src/schemas/number/core.ts @@ -0,0 +1,139 @@ +import type { Bounds, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' + + +export { number_ as number } + +interface number_ extends number_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function NumberSchema(src: unknown) { return typeof src === 'number' } +NumberSchema.tag = URI.number +NumberSchema.def = 0 + +const number_ = Object_assign( + NumberSchema, + userDefinitions, +) as number_ + +number_.min = function numberMin(minimum) { + return Object_assign( + boundedNumber({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +number_.max = function numberMax(maximum) { + return Object_assign( + boundedNumber({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +number_.moreThan = function numberMoreThan(exclusiveMinimum) { + return Object_assign( + boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), + { exclusiveMinimum }, + ) +} +number_.lessThan = function numberLessThan(exclusiveMaximum) { + return Object_assign( + boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), + { exclusiveMaximum }, + ) +} +number_.between = function numberBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedNumber({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + number_, + bindUserExtensions(number_, userExtensions), +) + +function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedNumberSchema(u: unknown) { + return typeof u === 'number' && within(bounds)(u) + }, carry, number_) +} + +declare namespace number_ { + interface core extends number_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.number + get def(): this['_type'] + minimum?: number + maximum?: number + exclusiveMinimum?: number + exclusiveMaximum?: number + } + interface methods { + min(minimum: Min): number_.Min + max(maximum: Max): number_.Max + moreThan(moreThan: Min): ExclusiveMin + lessThan(lessThan: Max): ExclusiveMax + between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.between<[min: X, max: Self['maximum']]> + : number_.min + ; + type Max + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.between<[min: Self['minimum'], max: X]> + : number_.max + ; + type ExclusiveMin + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.maxStrictMin<[min: X, Self['maximum']]> + : number_.moreThan + ; + type ExclusiveMax + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.minStrictMax<[Self['minimum'], min: X]> + : number_.lessThan + ; + interface min extends number_ { minimum: Min } + interface max extends number_ { maximum: Max } + interface moreThan extends number_ { exclusiveMinimum: Min } + interface lessThan extends number_ { exclusiveMaximum: Max } + interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } + interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } + interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } + interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } +} diff --git a/packages/schema/src/schemas/number/equals.ts b/packages/schema/src/schemas/number/equals.ts new file mode 100644 index 00000000..a599f588 --- /dev/null +++ b/packages/schema/src/schemas/number/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { SameValueNumber } from "@traversable/registry" + +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} diff --git a/packages/schema/src/schemas/object/core.ts b/packages/schema/src/schemas/object/core.ts new file mode 100644 index 00000000..4aa57c18 --- /dev/null +++ b/packages/schema/src/schemas/object/core.ts @@ -0,0 +1,79 @@ +import type { Force, SchemaOptions as Options, Unknown } from '@traversable/registry' +import { + applyOptions, + Array_isArray, + bindUserExtensions, + has, + _isPredicate, + Object_assign, + Object_keys, + record as record$, + object as object$, + isAnyObject, + symbol, + URI, +} from '@traversable/registry' + +import type { Entry, Optional, Required, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export { object_ as object } + +function object_< + S extends { [x: string]: Schema }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_< + S extends { [x: string]: SchemaLike }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_(schemas: { [x: string]: Schema }, options?: Options) { + return object_.def(schemas, options) +} + +interface object_ extends object_.core { + //<%= Types %> +} + +namespace object_ { + export let userDefinitions: Record = { + //<%= Definitions %> + } as object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ + /* v8 ignore next 1 */ + export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const keys = Object_keys(xs) + const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) + const req = keys.filter((k) => !has(symbol.optional)(xs[k])) + const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) + function ObjectSchema(src: unknown) { return predicate(src) } + ObjectSchema.tag = URI.object + ObjectSchema.def = xs + ObjectSchema.opt = opt + ObjectSchema.req = req + Object_assign(ObjectSchema, userDefinitions) + return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) + } +} + +declare namespace object_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: object_.type + tag: URI.object + get def(): S + opt: Optional + req: Required + } + type type< + S, + Opt extends Optional = Optional, + Req extends Required = Required, + T = Force< + & { [K in Req]-?: S[K]['_type' & keyof S[K]] } + & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } + > + > = never | T +} diff --git a/packages/schema/src/schemas/object/equals.ts b/packages/schema/src/schemas/object/equals.ts new file mode 100644 index 00000000..3cd8134f --- /dev/null +++ b/packages/schema/src/schemas/object/equals.ts @@ -0,0 +1,29 @@ +import type * as T from '@traversable/registry' +import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | T.Equal +export function equals(objectSchema: t.object): equals> +export function equals(objectSchema: t.object): equals> +export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { + function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + for (const k in def) { + const lHas = Object_hasOwn(l, k) + const rHas = Object_hasOwn(r, k) + if (lHas) { + if (!rHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (rHas) { + if (!lHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (!def[k].equals(l[k], r[k])) return false + } + return true + } + return objectEquals +} diff --git a/packages/schema/src/schemas/of/core.ts b/packages/schema/src/schemas/of/core.ts new file mode 100644 index 00000000..8f6f9030 --- /dev/null +++ b/packages/schema/src/schemas/of/core.ts @@ -0,0 +1,44 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +import type { + Entry, + Guard, + Guarded, + SchemaLike, +} from '@traversable/schema-core/namespace' + +export interface of extends of.core { + //<%= Types %> +} + +export function of(typeguard: S): Entry +export function of(typeguard: S): of +export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { + typeguard.def = typeguard + return Object_assign(typeguard, of.prototype) +} + +export namespace of { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(guard: T): of + /* v8 ignore next 6 */ + export function def(guard: T) { + function InlineSchema(src: unknown) { return guard(src) } + InlineSchema.tag = URI.inline + InlineSchema.def = guard + return InlineSchema + } +} + +export declare namespace of { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: Guarded + tag: URI.inline + get def(): S + } + type type> = never | T +} diff --git a/packages/schema/src/schemas/optional/core.ts b/packages/schema/src/schemas/optional/core.ts new file mode 100644 index 00000000..ba7120bc --- /dev/null +++ b/packages/schema/src/schemas/optional/core.ts @@ -0,0 +1,55 @@ +import type { Unknown } from '@traversable/registry' +import { + bindUserExtensions, + has, + _isPredicate, + optional as optional$, + Object_assign, + symbol, + URI, + isUnknown as isAny, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export function optional(schema: S): optional +export function optional(schema: S): optional> +export function optional(schema: S): optional { return optional.def(schema) } + +export interface optional extends optional.core { + //<%= Types %> +} + +export namespace optional { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): optional + export function def(x: T) { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = _isPredicate(x) ? optional$(x) : isAny + function OptionalSchema(src: unknown) { return predicate(src) } + OptionalSchema.tag = URI.optional + OptionalSchema.def = x + OptionalSchema[symbol.optional] = 1 + Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) + return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) + } + export const is + : (u: unknown) => u is optional + /* v8 ignore next 1 */ + = has('tag', (u) => u === URI.optional) +} + +export declare namespace optional { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.optional + _type: undefined | S['_type' & keyof S] + def: S + [symbol.optional]: number + } + export type type = never | T +} diff --git a/packages/schema/src/schemas/optional/equals.ts b/packages/schema/src/schemas/optional/equals.ts new file mode 100644 index 00000000..25944376 --- /dev/null +++ b/packages/schema/src/schemas/optional/equals.ts @@ -0,0 +1,13 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal +export function equals(optionalSchema: t.optional): equals +export function equals(optionalSchema: t.optional): equals +export function equals({ def }: t.optional<{ equals: Equal }>): Equal { + return function optionalEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + return def.equals(l, r) + } +} diff --git a/packages/schema/src/schemas/record/core.ts b/packages/schema/src/schemas/record/core.ts new file mode 100644 index 00000000..b5838df0 --- /dev/null +++ b/packages/schema/src/schemas/record/core.ts @@ -0,0 +1,50 @@ +import type { Unknown } from '@traversable/registry' +import { + isAnyObject, + record as record$, + bindUserExtensions, + _isPredicate, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export function record(schema: S): record +export function record(schema: S): record> +export function record(schema: Schema) { + return record.def(schema) +} + +export interface record extends record.core { + //<%= Types %> +} + +export namespace record { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): record + /* v8 ignore next 1 */ + export function def(x: unknown): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = _isPredicate(x) ? record$(x) : isAnyObject + function RecordSchema(src: unknown) { return predicate(src) } + RecordSchema.tag = URI.record + RecordSchema.def = x + Object_assign(RecordSchema, record.userDefinitions) + return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) + } +} + +export declare namespace record { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.record + get def(): S + _type: Record + } + export type type> = never | T +} diff --git a/packages/schema/src/schemas/record/equals.ts b/packages/schema/src/schemas/record/equals.ts new file mode 100644 index 00000000..07addff1 --- /dev/null +++ b/packages/schema/src/schemas/record/equals.ts @@ -0,0 +1,32 @@ +import type { Equal } from '@traversable/registry' +import { Array_isArray, Object_is, Object_keys, Object_hasOwn } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal +export function equals(recordSchema: t.record): equals +export function equals(recordSchema: t.record): equals +export function equals({ def }: t.record<{ equals: Equal }>): Equal> { + function recordEquals(l: Record, r: Record): boolean { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + const lhs = Object_keys(l) + const rhs = Object_keys(r) + let len = lhs.length + let k: string + if (len !== rhs.length) return false + for (let ix = len; ix-- !== 0;) { + k = lhs[ix] + if (!Object_hasOwn(r, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + len = rhs.length + for (let ix = len; ix-- !== 0;) { + k = rhs[ix] + if (!Object_hasOwn(l, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + return true + } + return recordEquals +} diff --git a/packages/schema/src/schemas/string/core.ts b/packages/schema/src/schemas/string/core.ts new file mode 100644 index 00000000..4276ca17 --- /dev/null +++ b/packages/schema/src/schemas/string/core.ts @@ -0,0 +1,102 @@ +import type { Bounds, Integer, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' + +export { string_ as string } + +/** @internal */ +function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedStringSchema(u: unknown) { + return string_(u) && within(bounds)(u.length) + }, carry, string_) +} + +interface string_ extends string_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function StringSchema(src: unknown) { return typeof src === 'string' } +StringSchema.tag = URI.string +StringSchema.def = '' + +const string_ = Object_assign( + StringSchema, + userDefinitions, +) as string_ + +string_.min = function stringMinLength(minLength) { + return Object_assign( + boundedString({ gte: minLength }, carryover(this, 'minLength')), + { minLength }, + ) +} +string_.max = function stringMaxLength(maxLength) { + return Object_assign( + boundedString({ lte: maxLength }, carryover(this, 'maxLength')), + { maxLength }, + ) +} +string_.between = function stringBetween( + min, + max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max)) { + return Object_assign( + boundedString({ gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) +} + +Object_assign( + string_, + bindUserExtensions(string_, userExtensions), +) + +declare namespace string_ { + interface core extends string_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: string + tag: URI.string + get def(): this['_type'] + } + interface methods { + minLength?: number + maxLength?: number + min>(minLength: Min): string_.Min + max>(maxLength: Max): string_.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maxLength: number }] + ? string_.between<[min: Min, max: Self['maxLength']]> + : string_.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? string_.between<[min: Self['minLength'], max: Max]> + : string_.max + ; + interface min extends string_ { minLength: Min } + interface max extends string_ { maxLength: Max } + interface between extends string_ { + minLength: Bounds[0] + maxLength: Bounds[1] + } +} diff --git a/packages/schema/src/schemas/string/equals.ts b/packages/schema/src/schemas/string/equals.ts new file mode 100644 index 00000000..b9444108 --- /dev/null +++ b/packages/schema/src/schemas/string/equals.ts @@ -0,0 +1,6 @@ +import type { Equal } from '@traversable/registry' + +export type equals = Equal +export function equals(left: string, right: string): boolean { + return left === right +} diff --git a/packages/schema/src/schemas/symbol/core.ts b/packages/schema/src/schemas/symbol/core.ts new file mode 100644 index 00000000..6e192b3e --- /dev/null +++ b/packages/schema/src/schemas/symbol/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { symbol_ as symbol } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface symbol_ extends symbol_.core { + //<%= Types %> +} + +function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } +SymbolSchema.tag = URI.symbol +SymbolSchema.def = Symbol() + +const symbol_ = Object_assign( + SymbolSchema, + userDefinitions, +) as symbol_ + +Object_assign(symbol_, userExtensions) + +declare namespace symbol_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.symbol + _type: symbol + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/symbol/equals.ts b/packages/schema/src/schemas/symbol/equals.ts new file mode 100644 index 00000000..24e82beb --- /dev/null +++ b/packages/schema/src/schemas/symbol/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: symbol, right: symbol): boolean { + return Object_is(left, right) +} diff --git a/packages/schema/src/schemas/tuple/core.ts b/packages/schema/src/schemas/tuple/core.ts new file mode 100644 index 00000000..2b277799 --- /dev/null +++ b/packages/schema/src/schemas/tuple/core.ts @@ -0,0 +1,86 @@ +import type { + SchemaOptions as Options, + TypeError, + Unknown +} from '@traversable/registry' + +import { + Array_isArray, + bindUserExtensions, + getConfig, + has, + _isPredicate, + Object_assign, + parseArgs, + symbol, + tuple as tuple$, + URI, +} from '@traversable/registry' + +import type { + Entry, + FirstOptionalItem, + invalid, + Schema, + SchemaLike, + TupleType, + ValidateTuple +} from '@traversable/schema-core/namespace' + +import type { optional } from '../optional/core.js' + +export { tuple } + +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { + return tuple.def(...parseArgs(getConfig().schema, args)) +} + +interface tuple extends tuple.core { + //<%= Types %> +} + +namespace tuple { + export let userDefinitions: Record = { + //<%= Definitions %> + } as tuple + export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple + /* v8 ignore next 1 */ + export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const opt = opt_ || xs.findIndex(has(symbol.optional)) + const options = { + ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) + } satisfies tuple.InternalOptions + const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) + function TupleSchema(src: unknown) { return predicate(src) } + TupleSchema.tag = URI.tuple + TupleSchema.def = xs + TupleSchema.opt = opt + Object_assign(TupleSchema, tuple.userDefinitions) + return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) + } +} + +declare namespace tuple { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.tuple + _type: TupleType + opt: FirstOptionalItem + def: S + } + type type> = never | T + type InternalOptions = { minLength?: number } + type validate = ValidateTuple> + + type from + = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T +} diff --git a/packages/schema/src/schemas/tuple/equals.ts b/packages/schema/src/schemas/tuple/equals.ts new file mode 100644 index 00000000..4d9de15c --- /dev/null +++ b/packages/schema/src/schemas/tuple/equals.ts @@ -0,0 +1,27 @@ +import type { Equal } from '@traversable/registry' +import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +export type equals = Equal + +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple) { + function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + for (let ix = tupleSchema.def.length; ix-- !== 0;) { + if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue + if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false + if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false + if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { + if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false + } + } + return true + } + return false + } + return tupleEquals +} diff --git a/packages/schema/src/schemas/undefined/core.ts b/packages/schema/src/schemas/undefined/core.ts new file mode 100644 index 00000000..0115f168 --- /dev/null +++ b/packages/schema/src/schemas/undefined/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { undefined_ as undefined } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface undefined_ extends undefined_.core { + //<%= Types %> +} + +function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } +UndefinedSchema.tag = URI.undefined +UndefinedSchema.def = void 0 as undefined + +const undefined_ = Object_assign( + UndefinedSchema, + userDefinitions, +) as undefined_ + +Object_assign(undefined_, userExtensions) + +declare namespace undefined_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.undefined + _type: undefined + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/undefined/equals.ts b/packages/schema/src/schemas/undefined/equals.ts new file mode 100644 index 00000000..2836cd51 --- /dev/null +++ b/packages/schema/src/schemas/undefined/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: undefined, right: undefined): boolean { + return Object_is(left, right) +} diff --git a/packages/schema/src/schemas/union/core.ts b/packages/schema/src/schemas/union/core.ts new file mode 100644 index 00000000..33698c38 --- /dev/null +++ b/packages/schema/src/schemas/union/core.ts @@ -0,0 +1,50 @@ +import type { Unknown } from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + isUnknown as isAny, + Object_assign, + union as union$, + URI, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' + +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: unknown[]) { + return union.def(schemas) +} + +export interface union extends union.core { + //<%= Types %> +} + +export namespace union { + export let userDefinitions: Record = { + //<%= Definitions %> + } as Partial> + export function def(xs: T): union + /* v8 ignore next 1 */ + export function def(xs: unknown[]) { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = xs.every(_isPredicate) ? union$(xs) : isAny + function UnionSchema(src: unknown): src is unknown { return predicate(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.userDefinitions) + return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) + } +} + +export declare namespace union { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.union + _type: union.type + get def(): S + } + type type = never | T +} diff --git a/packages/schema/src/schemas/union/equals.ts b/packages/schema/src/schemas/union/equals.ts new file mode 100644 index 00000000..c0275e69 --- /dev/null +++ b/packages/schema/src/schemas/union/equals.ts @@ -0,0 +1,16 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = Equal +export function equals(unionSchema: t.union<[...S]>): equals +export function equals(unionSchema: t.union<[...S]>): equals +export function equals({ def }: t.union<{ equals: Equal }[]>): Equal { + function unionEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (def[ix].equals(l, r)) return true + return false + } + return unionEquals +} diff --git a/packages/schema/src/schemas/unknown/core.ts b/packages/schema/src/schemas/unknown/core.ts new file mode 100644 index 00000000..0a190db3 --- /dev/null +++ b/packages/schema/src/schemas/unknown/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { unknown_ as unknown } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface unknown_ extends unknown_.core { + //<%= Types %> +} + +function UnknownSchema(src: unknown): src is unknown { return true } +UnknownSchema.tag = URI.unknown +UnknownSchema.def = void 0 as unknown + +const unknown_ = Object_assign( + UnknownSchema, + userDefinitions, +) as unknown_ + +Object_assign(unknown_, userExtensions) + +declare namespace unknown_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.unknown + _type: unknown + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/unknown/equals.ts b/packages/schema/src/schemas/unknown/equals.ts new file mode 100644 index 00000000..41b30fd5 --- /dev/null +++ b/packages/schema/src/schemas/unknown/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: any, right: any): boolean { + return Object_is(left, right) +} diff --git a/packages/schema/src/schemas/void/core.ts b/packages/schema/src/schemas/void/core.ts new file mode 100644 index 00000000..d178213e --- /dev/null +++ b/packages/schema/src/schemas/void/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { void_ as void, void_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface void_ extends void_.core { + //<%= Types %> +} + +function VoidSchema(src: unknown): src is void { return src === void 0 } +VoidSchema.tag = URI.void +VoidSchema.def = void 0 as void + +const void_ = Object_assign( + VoidSchema, + userDefinitions, +) as void_ + +Object_assign(void_, userExtensions) + +declare namespace void_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.void + _type: void + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/void/equals.ts b/packages/schema/src/schemas/void/equals.ts new file mode 100644 index 00000000..6f7b9779 --- /dev/null +++ b/packages/schema/src/schemas/void/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: void, right: void): boolean { + return Object_is(left, right) +} From 7cdf5aa16b7f4e23dbcd6bde6803e54443c3e97a Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sun, 13 Apr 2025 09:07:22 -0500 Subject: [PATCH 30/45] feat(schema): generates all targets --- packages/derive-validators/src/schemas/any.ts | 10 ++ .../derive-validators/src/schemas/array.ts | 27 +++++ .../derive-validators/src/schemas/bigint.ts | 13 +++ .../derive-validators/src/schemas/boolean.ts | 12 ++ packages/derive-validators/src/schemas/eq.ts | 17 +++ .../derive-validators/src/schemas/integer.ts | 13 +++ .../src/schemas/intersect.ts | 21 ++++ .../derive-validators/src/schemas/never.ts | 10 ++ .../derive-validators/src/schemas/null.ts | 13 +++ .../derive-validators/src/schemas/number.ts | 13 +++ .../derive-validators/src/schemas/object.ts | 110 ++++++++++++++++++ .../derive-validators/src/schemas/optional.ts | 17 +++ .../derive-validators/src/schemas/record.ts | 24 ++++ .../derive-validators/src/schemas/string.ts | 13 +++ .../derive-validators/src/schemas/symbol.ts | 13 +++ .../derive-validators/src/schemas/tuple.ts | 34 ++++++ .../src/schemas/undefined.ts | 13 +++ .../derive-validators/src/schemas/union.ts | 26 +++++ .../derive-validators/src/schemas/unknown.ts | 10 ++ .../derive-validators/src/schemas/void.ts | 13 +++ .../schema-to-json-schema/src/schemas/any.ts | 5 + .../src/schemas/array.ts | 36 ++++++ .../src/schemas/bigint.ts | 7 ++ .../src/schemas/boolean.ts | 5 + .../schema-to-json-schema/src/schemas/eq.ts | 8 ++ .../src/schemas/integer.ts | 23 ++++ .../src/schemas/intersect.ts | 20 ++++ .../src/schemas/never.ts | 5 + .../schema-to-json-schema/src/schemas/null.ts | 5 + .../src/schemas/number.ts | 23 ++++ .../src/schemas/object.ts | 27 +++++ .../src/schemas/optional.ts | 19 +++ .../src/schemas/record.ts | 21 ++++ .../src/schemas/string.ts | 22 ++++ .../src/schemas/symbol.ts | 5 + .../src/schemas/tuple.ts | 37 ++++++ .../src/schemas/undefined.ts | 5 + .../src/schemas/union.ts | 17 +++ .../src/schemas/unknown.ts | 5 + .../schema-to-json-schema/src/schemas/void.ts | 7 ++ packages/schema-to-string/src/schemas/any.ts | 2 + .../schema-to-string/src/schemas/array.ts | 22 ++++ .../schema-to-string/src/schemas/bigint.ts | 2 + .../schema-to-string/src/schemas/boolean.ts | 2 + packages/schema-to-string/src/schemas/eq.ts | 17 +++ .../schema-to-string/src/schemas/integer.ts | 2 + .../schema-to-string/src/schemas/intersect.ts | 18 +++ .../schema-to-string/src/schemas/never.ts | 2 + packages/schema-to-string/src/schemas/null.ts | 2 + .../schema-to-string/src/schemas/number.ts | 2 + .../schema-to-string/src/schemas/object.ts | 41 +++++++ .../schema-to-string/src/schemas/optional.ts | 15 +++ .../schema-to-string/src/schemas/record.ts | 17 +++ .../schema-to-string/src/schemas/string.ts | 2 + .../schema-to-string/src/schemas/symbol.ts | 2 + .../schema-to-string/src/schemas/tuple.ts | 26 +++++ .../schema-to-string/src/schemas/undefined.ts | 2 + .../schema-to-string/src/schemas/union.ts | 18 +++ .../schema-to-string/src/schemas/unknown.ts | 2 + packages/schema-to-string/src/schemas/void.ts | 2 + packages/schema/src/_exports.ts | 2 + packages/schema/src/_namespace.ts | 32 +++++ .../schema/src/schemas/any/toJsonSchema.ts | 5 + packages/schema/src/schemas/any/toString.ts | 2 + packages/schema/src/schemas/any/validate.ts | 10 ++ .../schema/src/schemas/array/toJsonSchema.ts | 36 ++++++ packages/schema/src/schemas/array/toString.ts | 22 ++++ packages/schema/src/schemas/array/validate.ts | 27 +++++ .../schema/src/schemas/bigint/toJsonSchema.ts | 7 ++ .../schema/src/schemas/bigint/toString.ts | 2 + .../schema/src/schemas/bigint/validate.ts | 13 +++ .../src/schemas/boolean/toJsonSchema.ts | 5 + .../schema/src/schemas/boolean/toString.ts | 2 + .../schema/src/schemas/boolean/validate.ts | 12 ++ .../schema/src/schemas/eq/toJsonSchema.ts | 8 ++ packages/schema/src/schemas/eq/toString.ts | 17 +++ packages/schema/src/schemas/eq/validate.ts | 17 +++ .../src/schemas/integer/toJsonSchema.ts | 23 ++++ .../schema/src/schemas/integer/toString.ts | 2 + .../schema/src/schemas/integer/validate.ts | 13 +++ .../src/schemas/intersect/toJsonSchema.ts | 20 ++++ .../schema/src/schemas/intersect/toString.ts | 18 +++ .../schema/src/schemas/intersect/validate.ts | 21 ++++ .../schema/src/schemas/never/toJsonSchema.ts | 5 + packages/schema/src/schemas/never/toString.ts | 2 + packages/schema/src/schemas/never/validate.ts | 10 ++ .../schema/src/schemas/null/toJsonSchema.ts | 5 + packages/schema/src/schemas/null/toString.ts | 2 + packages/schema/src/schemas/null/validate.ts | 13 +++ .../schema/src/schemas/number/toJsonSchema.ts | 23 ++++ .../schema/src/schemas/number/toString.ts | 2 + .../schema/src/schemas/number/validate.ts | 13 +++ .../schema/src/schemas/object/toJsonSchema.ts | 27 +++++ .../schema/src/schemas/object/toString.ts | 41 +++++++ .../schema/src/schemas/object/validate.ts | 110 ++++++++++++++++++ .../src/schemas/optional/toJsonSchema.ts | 19 +++ .../schema/src/schemas/optional/toString.ts | 15 +++ .../schema/src/schemas/optional/validate.ts | 17 +++ .../schema/src/schemas/record/toJsonSchema.ts | 21 ++++ .../schema/src/schemas/record/toString.ts | 17 +++ .../schema/src/schemas/record/validate.ts | 24 ++++ .../schema/src/schemas/string/toJsonSchema.ts | 22 ++++ .../schema/src/schemas/string/toString.ts | 2 + .../schema/src/schemas/string/validate.ts | 13 +++ .../schema/src/schemas/symbol/toJsonSchema.ts | 5 + .../schema/src/schemas/symbol/toString.ts | 2 + .../schema/src/schemas/symbol/validate.ts | 13 +++ .../schema/src/schemas/tuple/toJsonSchema.ts | 37 ++++++ packages/schema/src/schemas/tuple/toString.ts | 26 +++++ packages/schema/src/schemas/tuple/validate.ts | 34 ++++++ .../src/schemas/undefined/toJsonSchema.ts | 5 + .../schema/src/schemas/undefined/toString.ts | 2 + .../schema/src/schemas/undefined/validate.ts | 13 +++ .../schema/src/schemas/union/toJsonSchema.ts | 17 +++ packages/schema/src/schemas/union/toString.ts | 18 +++ packages/schema/src/schemas/union/validate.ts | 26 +++++ .../src/schemas/unknown/toJsonSchema.ts | 5 + .../schema/src/schemas/unknown/toString.ts | 2 + .../schema/src/schemas/unknown/validate.ts | 10 ++ .../schema/src/schemas/void/toJsonSchema.ts | 7 ++ packages/schema/src/schemas/void/toString.ts | 2 + packages/schema/src/schemas/void/validate.ts | 13 +++ 122 files changed, 1878 insertions(+) create mode 100644 packages/derive-validators/src/schemas/any.ts create mode 100644 packages/derive-validators/src/schemas/array.ts create mode 100644 packages/derive-validators/src/schemas/bigint.ts create mode 100644 packages/derive-validators/src/schemas/boolean.ts create mode 100644 packages/derive-validators/src/schemas/eq.ts create mode 100644 packages/derive-validators/src/schemas/integer.ts create mode 100644 packages/derive-validators/src/schemas/intersect.ts create mode 100644 packages/derive-validators/src/schemas/never.ts create mode 100644 packages/derive-validators/src/schemas/null.ts create mode 100644 packages/derive-validators/src/schemas/number.ts create mode 100644 packages/derive-validators/src/schemas/object.ts create mode 100644 packages/derive-validators/src/schemas/optional.ts create mode 100644 packages/derive-validators/src/schemas/record.ts create mode 100644 packages/derive-validators/src/schemas/string.ts create mode 100644 packages/derive-validators/src/schemas/symbol.ts create mode 100644 packages/derive-validators/src/schemas/tuple.ts create mode 100644 packages/derive-validators/src/schemas/undefined.ts create mode 100644 packages/derive-validators/src/schemas/union.ts create mode 100644 packages/derive-validators/src/schemas/unknown.ts create mode 100644 packages/derive-validators/src/schemas/void.ts create mode 100644 packages/schema-to-json-schema/src/schemas/any.ts create mode 100644 packages/schema-to-json-schema/src/schemas/array.ts create mode 100644 packages/schema-to-json-schema/src/schemas/bigint.ts create mode 100644 packages/schema-to-json-schema/src/schemas/boolean.ts create mode 100644 packages/schema-to-json-schema/src/schemas/eq.ts create mode 100644 packages/schema-to-json-schema/src/schemas/integer.ts create mode 100644 packages/schema-to-json-schema/src/schemas/intersect.ts create mode 100644 packages/schema-to-json-schema/src/schemas/never.ts create mode 100644 packages/schema-to-json-schema/src/schemas/null.ts create mode 100644 packages/schema-to-json-schema/src/schemas/number.ts create mode 100644 packages/schema-to-json-schema/src/schemas/object.ts create mode 100644 packages/schema-to-json-schema/src/schemas/optional.ts create mode 100644 packages/schema-to-json-schema/src/schemas/record.ts create mode 100644 packages/schema-to-json-schema/src/schemas/string.ts create mode 100644 packages/schema-to-json-schema/src/schemas/symbol.ts create mode 100644 packages/schema-to-json-schema/src/schemas/tuple.ts create mode 100644 packages/schema-to-json-schema/src/schemas/undefined.ts create mode 100644 packages/schema-to-json-schema/src/schemas/union.ts create mode 100644 packages/schema-to-json-schema/src/schemas/unknown.ts create mode 100644 packages/schema-to-json-schema/src/schemas/void.ts create mode 100644 packages/schema-to-string/src/schemas/any.ts create mode 100644 packages/schema-to-string/src/schemas/array.ts create mode 100644 packages/schema-to-string/src/schemas/bigint.ts create mode 100644 packages/schema-to-string/src/schemas/boolean.ts create mode 100644 packages/schema-to-string/src/schemas/eq.ts create mode 100644 packages/schema-to-string/src/schemas/integer.ts create mode 100644 packages/schema-to-string/src/schemas/intersect.ts create mode 100644 packages/schema-to-string/src/schemas/never.ts create mode 100644 packages/schema-to-string/src/schemas/null.ts create mode 100644 packages/schema-to-string/src/schemas/number.ts create mode 100644 packages/schema-to-string/src/schemas/object.ts create mode 100644 packages/schema-to-string/src/schemas/optional.ts create mode 100644 packages/schema-to-string/src/schemas/record.ts create mode 100644 packages/schema-to-string/src/schemas/string.ts create mode 100644 packages/schema-to-string/src/schemas/symbol.ts create mode 100644 packages/schema-to-string/src/schemas/tuple.ts create mode 100644 packages/schema-to-string/src/schemas/undefined.ts create mode 100644 packages/schema-to-string/src/schemas/union.ts create mode 100644 packages/schema-to-string/src/schemas/unknown.ts create mode 100644 packages/schema-to-string/src/schemas/void.ts create mode 100644 packages/schema/src/_exports.ts create mode 100644 packages/schema/src/_namespace.ts create mode 100644 packages/schema/src/schemas/any/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/any/toString.ts create mode 100644 packages/schema/src/schemas/any/validate.ts create mode 100644 packages/schema/src/schemas/array/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/array/toString.ts create mode 100644 packages/schema/src/schemas/array/validate.ts create mode 100644 packages/schema/src/schemas/bigint/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/bigint/toString.ts create mode 100644 packages/schema/src/schemas/bigint/validate.ts create mode 100644 packages/schema/src/schemas/boolean/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/boolean/toString.ts create mode 100644 packages/schema/src/schemas/boolean/validate.ts create mode 100644 packages/schema/src/schemas/eq/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/eq/toString.ts create mode 100644 packages/schema/src/schemas/eq/validate.ts create mode 100644 packages/schema/src/schemas/integer/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/integer/toString.ts create mode 100644 packages/schema/src/schemas/integer/validate.ts create mode 100644 packages/schema/src/schemas/intersect/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/intersect/toString.ts create mode 100644 packages/schema/src/schemas/intersect/validate.ts create mode 100644 packages/schema/src/schemas/never/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/never/toString.ts create mode 100644 packages/schema/src/schemas/never/validate.ts create mode 100644 packages/schema/src/schemas/null/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/null/toString.ts create mode 100644 packages/schema/src/schemas/null/validate.ts create mode 100644 packages/schema/src/schemas/number/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/number/toString.ts create mode 100644 packages/schema/src/schemas/number/validate.ts create mode 100644 packages/schema/src/schemas/object/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/object/toString.ts create mode 100644 packages/schema/src/schemas/object/validate.ts create mode 100644 packages/schema/src/schemas/optional/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/optional/toString.ts create mode 100644 packages/schema/src/schemas/optional/validate.ts create mode 100644 packages/schema/src/schemas/record/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/record/toString.ts create mode 100644 packages/schema/src/schemas/record/validate.ts create mode 100644 packages/schema/src/schemas/string/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/string/toString.ts create mode 100644 packages/schema/src/schemas/string/validate.ts create mode 100644 packages/schema/src/schemas/symbol/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/symbol/toString.ts create mode 100644 packages/schema/src/schemas/symbol/validate.ts create mode 100644 packages/schema/src/schemas/tuple/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/tuple/toString.ts create mode 100644 packages/schema/src/schemas/tuple/validate.ts create mode 100644 packages/schema/src/schemas/undefined/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/undefined/toString.ts create mode 100644 packages/schema/src/schemas/undefined/validate.ts create mode 100644 packages/schema/src/schemas/union/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/union/toString.ts create mode 100644 packages/schema/src/schemas/union/validate.ts create mode 100644 packages/schema/src/schemas/unknown/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/unknown/toString.ts create mode 100644 packages/schema/src/schemas/unknown/validate.ts create mode 100644 packages/schema/src/schemas/void/toJsonSchema.ts create mode 100644 packages/schema/src/schemas/void/toString.ts create mode 100644 packages/schema/src/schemas/void/validate.ts diff --git a/packages/derive-validators/src/schemas/any.ts b/packages/derive-validators/src/schemas/any.ts new file mode 100644 index 00000000..9c4298c0 --- /dev/null +++ b/packages/derive-validators/src/schemas/any.ts @@ -0,0 +1,10 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.unknown): validate { + validateUnknown.tag = URI.unknown + function validateUnknown() { return true as const } + return validateUnknown +} diff --git a/packages/derive-validators/src/schemas/array.ts b/packages/derive-validators/src/schemas/array.ts new file mode 100644 index 00000000..4d8e7958 --- /dev/null +++ b/packages/derive-validators/src/schemas/array.ts @@ -0,0 +1,27 @@ +import { URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { Errors, NullaryErrors } from '@traversable/derive-validators' + +export type validate = never | ValidationFn +export function validate(arraySchema: t.array): validate +export function validate(arraySchema: t.array): validate +export function validate( + { def: { validate = () => true }, minLength, maxLength }: t.array +) { + validateArray.tag = URI.array + function validateArray(u: unknown, path = Array.of()) { + if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] + let errors = Array.of() + if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) + if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) + for (let i = 0, len = u.length; i < len; i++) { + let y = u[i] + let results = validate(y, [...path, i]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateArray +} diff --git a/packages/derive-validators/src/schemas/bigint.ts b/packages/derive-validators/src/schemas/bigint.ts new file mode 100644 index 00000000..7e3284da --- /dev/null +++ b/packages/derive-validators/src/schemas/bigint.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(bigIntSchema: S): validate { + validateBigInt.tag = URI.bigint + function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { + return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] + } + return validateBigInt +} diff --git a/packages/derive-validators/src/schemas/boolean.ts b/packages/derive-validators/src/schemas/boolean.ts new file mode 100644 index 00000000..76cee261 --- /dev/null +++ b/packages/derive-validators/src/schemas/boolean.ts @@ -0,0 +1,12 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(booleanSchema: t.boolean): validate { + validateBoolean.tag = URI.boolean + function validateBoolean(u: unknown, path = Array.of()) { + return booleanSchema(true as const) || [NullaryErrors.null(u, path)] + } + return validateBoolean +} diff --git a/packages/derive-validators/src/schemas/eq.ts b/packages/derive-validators/src/schemas/eq.ts new file mode 100644 index 00000000..2f02ba7d --- /dev/null +++ b/packages/derive-validators/src/schemas/eq.ts @@ -0,0 +1,17 @@ +import { Equal, getConfig, URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { Validate } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + +export type validate = Validate +export function validate(eqSchema: t.eq): validate +export function validate({ def }: t.eq): validate { + validateEq.tag = URI.eq + function validateEq(u: unknown, path = Array.of()) { + let options = getConfig().schema + let equals = options?.eq?.equalsFn || Equal.lax + if (equals(def, u)) return true + else return [Errors.eq(u, path, def)] + } + return validateEq +} diff --git a/packages/derive-validators/src/schemas/integer.ts b/packages/derive-validators/src/schemas/integer.ts new file mode 100644 index 00000000..5f044c5d --- /dev/null +++ b/packages/derive-validators/src/schemas/integer.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(integerSchema: S): validate { + validateInteger.tag = URI.integer + function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { + return integerSchema(u) || [NullaryErrors.integer(u, path)] + } + return validateInteger +} diff --git a/packages/derive-validators/src/schemas/intersect.ts b/packages/derive-validators/src/schemas/intersect.ts new file mode 100644 index 00000000..8a586df2 --- /dev/null +++ b/packages/derive-validators/src/schemas/intersect.ts @@ -0,0 +1,21 @@ +import { URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(intersectSchema: t.intersect): validate +export function validate(intersectSchema: t.intersect): validate +export function validate({ def }: t.intersect) { + validateIntersect.tag = URI.intersect + function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results !== true) + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + return errors.length === 0 || errors + } + return validateIntersect +} diff --git a/packages/derive-validators/src/schemas/never.ts b/packages/derive-validators/src/schemas/never.ts new file mode 100644 index 00000000..52ce9ee5 --- /dev/null +++ b/packages/derive-validators/src/schemas/never.ts @@ -0,0 +1,10 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.never): validate { + validateNever.tag = URI.never + function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } + return validateNever +} diff --git a/packages/derive-validators/src/schemas/null.ts b/packages/derive-validators/src/schemas/null.ts new file mode 100644 index 00000000..b2abe36b --- /dev/null +++ b/packages/derive-validators/src/schemas/null.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(nullSchema: t.null): validate { + validateNull.tag = URI.null + function validateNull(u: unknown, path = Array.of()) { + return nullSchema(u) || [NullaryErrors.null(u, path)] + } + return validateNull +} diff --git a/packages/derive-validators/src/schemas/number.ts b/packages/derive-validators/src/schemas/number.ts new file mode 100644 index 00000000..416f639b --- /dev/null +++ b/packages/derive-validators/src/schemas/number.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(numberSchema: S): validate { + validateNumber.tag = URI.number + function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return numberSchema(u) || [NullaryErrors.number(u, path)] + } + return validateNumber +} diff --git a/packages/derive-validators/src/schemas/object.ts b/packages/derive-validators/src/schemas/object.ts new file mode 100644 index 00000000..576d5aaf --- /dev/null +++ b/packages/derive-validators/src/schemas/object.ts @@ -0,0 +1,110 @@ +import { + Array_isArray, + has, + Object_keys, + Object_hasOwn, + typeName, + URI, +} from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { getConfig } from '@traversable/schema-core' +import type { ValidationError, Validator, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors, Errors, UnaryErrors } from '@traversable/derive-validators' + +/** @internal */ +let isObject = (u: unknown): u is { [x: string]: unknown } => + !!u && typeof u === 'object' && !Array_isArray(u) + +/** @internal */ +let isKeyOf = (k: keyof any, u: T): k is keyof T => + !!u && (typeof u === 'function' || typeof u === 'object') && k in u + +/** @internal */ +let isOptional = has('tag', (tag) => tag === URI.optional) + + +export type validate = never | ValidationFn + +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { + validateObject.tag = URI.object + function validateObject(u: unknown, path_ = Array.of()) { + // if (objectSchema(u)) return true + if (!isObject(u)) return [Errors.object(u, path_)] + let errors = Array.of() + let { schema: { optionalTreatment } } = getConfig() + let keys = Object_keys(objectSchema.def) + if (optionalTreatment === 'exactOptional') { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (Object_hasOwn(u, k) && u[k] === undefined) { + if (isOptional(objectSchema.def[k].validate)) { + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] + errors.push(NullaryErrors[tag](...args)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + errors.push(NullaryErrors[tag](u[k], path, tag)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag].invalid(u[k], path)) + } + errors.push(...results) + } + else if (Object_hasOwn(u, k)) { + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + errors.push(...results) + continue + } else { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + } + } + else { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (!Object_hasOwn(u, k)) { + if (!isOptional(objectSchema.def[k].validate)) { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + else { + if (!Object_hasOwn(u, k)) continue + if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { + if (u[k] === undefined) continue + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let j = 0; j < results.length; j++) { + let result = results[j] + errors.push(result) + continue + } + } + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let l = 0; l < results.length; l++) { + let result = results[l] + errors.push(result) + } + } + } + return errors.length === 0 || errors + } + + return validateObject +} diff --git a/packages/derive-validators/src/schemas/optional.ts b/packages/derive-validators/src/schemas/optional.ts new file mode 100644 index 00000000..feb93d53 --- /dev/null +++ b/packages/derive-validators/src/schemas/optional.ts @@ -0,0 +1,17 @@ +import { URI } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import type { Validate, Validator, ValidationFn } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(optionalSchema: t.optional): validate +export function validate(optionalSchema: t.optional): validate +export function validate({ def }: t.optional): ValidationFn { + validateOptional.tag = URI.optional + validateOptional.optional = 1 + function validateOptional(u: unknown, path = Array.of()) { + if (u === void 0) return true + return def.validate(u, path) + } + return validateOptional +} diff --git a/packages/derive-validators/src/schemas/record.ts b/packages/derive-validators/src/schemas/record.ts new file mode 100644 index 00000000..6d958004 --- /dev/null +++ b/packages/derive-validators/src/schemas/record.ts @@ -0,0 +1,24 @@ +import type { t } from '@traversable/schema-core' +import { Array_isArray, Object_keys, URI } from '@traversable/registry' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = never | ValidationFn +export function validate(recordSchema: t.record): validate +export function validate(recordSchema: t.record): validate +export function validate({ def: { validate = () => true } }: t.record) { + validateRecord.tag = URI.record + function validateRecord(u: unknown, path = Array.of()) { + if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] + let errors = Array.of() + let keys = Object_keys(u) + for (let k of keys) { + let y = u[k] + let results = validate(y, [...path, k]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateRecord +} diff --git a/packages/derive-validators/src/schemas/string.ts b/packages/derive-validators/src/schemas/string.ts new file mode 100644 index 00000000..650ce686 --- /dev/null +++ b/packages/derive-validators/src/schemas/string.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(stringSchema: S): validate { + validateString.tag = URI.string + function validateString(u: unknown, path = Array.of()): true | ValidationError[] { + return stringSchema(u) || [NullaryErrors.number(u, path)] + } + return validateString +} diff --git a/packages/derive-validators/src/schemas/symbol.ts b/packages/derive-validators/src/schemas/symbol.ts new file mode 100644 index 00000000..302e11f2 --- /dev/null +++ b/packages/derive-validators/src/schemas/symbol.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(symbolSchema: t.symbol): validate { + validateSymbol.tag = URI.symbol + function validateSymbol(u: unknown, path = Array.of()) { + return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] + } + return validateSymbol +} diff --git a/packages/derive-validators/src/schemas/tuple.ts b/packages/derive-validators/src/schemas/tuple.ts new file mode 100644 index 00000000..d84e3650 --- /dev/null +++ b/packages/derive-validators/src/schemas/tuple.ts @@ -0,0 +1,34 @@ +import { URI, Array_isArray } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + +export type validate = Validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): Validate { + validateTuple.tag = URI.tuple + function validateTuple(u: unknown, path = Array.of()) { + let errors = Array.of() + if (!Array_isArray(u)) return [Errors.array(u, path)] + for (let i = 0; i < tupleSchema.def.length; i++) { + if (!(i in u) && !(t.optional.is(tupleSchema.def[i].validate))) { + errors.push(Errors.missingIndex(u, [...path, i])) + continue + } + let results = tupleSchema.def[i].validate(u[i], [...path, i]) + if (results !== true) { + for (let j = 0; j < results.length; j++) errors.push(results[j]) + results.push(Errors.arrayElement(u[i], [...path, i])) + } + } + if (u.length > tupleSchema.def.length) { + for (let k = tupleSchema.def.length; k < u.length; k++) { + let excess = u[k] + errors.push(Errors.excessItems(excess, [...path, k])) + } + } + return errors.length === 0 || errors + } + return validateTuple +} diff --git a/packages/derive-validators/src/schemas/undefined.ts b/packages/derive-validators/src/schemas/undefined.ts new file mode 100644 index 00000000..d69c9d9e --- /dev/null +++ b/packages/derive-validators/src/schemas/undefined.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(undefinedSchema: t.undefined): validate { + validateUndefined.tag = URI.undefined + function validateUndefined(u: unknown, path = Array.of()) { + return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] + } + return validateUndefined +} diff --git a/packages/derive-validators/src/schemas/union.ts b/packages/derive-validators/src/schemas/union.ts new file mode 100644 index 00000000..ba69fccf --- /dev/null +++ b/packages/derive-validators/src/schemas/union.ts @@ -0,0 +1,26 @@ +import { URI } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(unionSchema: t.union): validate +export function validate(unionSchema: t.union): validate +export function validate({ def }: t.union) { + validateUnion.tag = URI.union + function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { + // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results === true) { + // validateUnion.optional = 0 + return true + } + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + // validateUnion.optional = 0 + return errors.length === 0 || errors + } + return validateUnion +} diff --git a/packages/derive-validators/src/schemas/unknown.ts b/packages/derive-validators/src/schemas/unknown.ts new file mode 100644 index 00000000..d286c4b5 --- /dev/null +++ b/packages/derive-validators/src/schemas/unknown.ts @@ -0,0 +1,10 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.any): validate { + validateAny.tag = URI.any + function validateAny() { return true as const } + return validateAny +} diff --git a/packages/derive-validators/src/schemas/void.ts b/packages/derive-validators/src/schemas/void.ts new file mode 100644 index 00000000..a67fc4e4 --- /dev/null +++ b/packages/derive-validators/src/schemas/void.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(voidSchema: t.void): validate { + validateVoid.tag = URI.void + function validateVoid(u: unknown, path = Array.of()) { + return voidSchema(u) || [NullaryErrors.void(u, path)] + } + return validateVoid +} diff --git a/packages/schema-to-json-schema/src/schemas/any.ts b/packages/schema-to-json-schema/src/schemas/any.ts new file mode 100644 index 00000000..25336fc6 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/any.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return unknownToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/array.ts b/packages/schema-to-json-schema/src/schemas/array.ts new file mode 100644 index 00000000..cbdda659 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/array.ts @@ -0,0 +1,36 @@ +import type { t } from '@traversable/schema-core' +import type * as T from '@traversable/registry' +import type { SizeBounds } from '@traversable/schema-to-json-schema' +import { hasSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): never | T.Force< + & { type: 'array', items: T.Returns } + & T.PickIfDefined + > +} + +export function toJsonSchema>(arraySchema: T): toJsonSchema +export function toJsonSchema(arraySchema: T): toJsonSchema +export function toJsonSchema( + { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, +): () => { + type: 'array' + items: unknown + minLength?: number + maxLength?: number +} { + function arrayToJsonSchema() { + let items = hasSchema(def) ? def.toJsonSchema() : def + let out = { + type: 'array' as const, + items, + minLength, + maxLength, + } + if (typeof minLength !== 'number') delete out.minLength + if (typeof maxLength !== 'number') delete out.maxLength + return out + } + return arrayToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/bigint.ts b/packages/schema-to-json-schema/src/schemas/bigint.ts new file mode 100644 index 00000000..f6c7bc5b --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/bigint.ts @@ -0,0 +1,7 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function bigintToJsonSchema(): void { + return void 0 + } + return bigintToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/boolean.ts b/packages/schema-to-json-schema/src/schemas/boolean.ts new file mode 100644 index 00000000..d1b86f70 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/boolean.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'boolean' } } +export function toJsonSchema(): toJsonSchema { + function booleanToJsonSchema() { return { type: 'boolean' as const } } + return booleanToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/eq.ts b/packages/schema-to-json-schema/src/schemas/eq.ts new file mode 100644 index 00000000..8edfd03a --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/eq.ts @@ -0,0 +1,8 @@ +import type { t } from '@traversable/schema-core' + +export interface toJsonSchema { (): { const: T } } +export function toJsonSchema(eqSchema: t.eq): toJsonSchema +export function toJsonSchema({ def }: t.eq) { + function eqToJsonSchema() { return { const: def } } + return eqToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/integer.ts b/packages/schema-to-json-schema/src/schemas/integer.ts new file mode 100644 index 00000000..d531e292 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/integer.ts @@ -0,0 +1,23 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.integer): toJsonSchema { + function integerToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'integer' as const, + ...bounds, + } + } + return integerToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/intersect.ts b/packages/schema-to-json-schema/src/schemas/intersect.ts new file mode 100644 index 00000000..d3942a94 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/intersect.ts @@ -0,0 +1,20 @@ +import type { Returns } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + allOf: { [I in keyof T]: Returns } + } +} + +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema({ def }: t.intersect): () => {} { + function intersectToJsonSchema() { + return { + allOf: def.map(getSchema) + } + } + return intersectToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/never.ts b/packages/schema-to-json-schema/src/schemas/never.ts new file mode 100644 index 00000000..d22338df --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/never.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): never } +export function toJsonSchema(): toJsonSchema { + function neverToJsonSchema() { return void 0 as never } + return neverToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/null.ts b/packages/schema-to-json-schema/src/schemas/null.ts new file mode 100644 index 00000000..7a3b7c3a --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/null.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'null', enum: [null] } } +export function toJsonSchema(): toJsonSchema { + function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } + return nullToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/number.ts b/packages/schema-to-json-schema/src/schemas/number.ts new file mode 100644 index 00000000..7146d478 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/number.ts @@ -0,0 +1,23 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.number): toJsonSchema { + function numberToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'number' as const, + ...bounds, + } + } + return numberToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/object.ts b/packages/schema-to-json-schema/src/schemas/object.ts new file mode 100644 index 00000000..bc79c90c --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/object.ts @@ -0,0 +1,27 @@ +import type { Returns } from '@traversable/registry' +import { fn, Object_keys } from '@traversable/registry' +import type { RequiredKeys } from '@traversable/schema-to-json-schema' +import { isRequired, property } from '@traversable/schema-to-json-schema' +import { t } from '@traversable/schema-core' + +export interface toJsonSchema = RequiredKeys> { + (): { + type: 'object' + required: { [I in keyof KS]: KS[I] & string } + properties: { [K in keyof T]: Returns } + } +} + +export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { + const required = Object_keys(def).filter(isRequired(def)) + function objectToJsonSchema() { + return { + type: 'object' as const, + required, + properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), + } + } + return objectToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/optional.ts b/packages/schema-to-json-schema/src/schemas/optional.ts new file mode 100644 index 00000000..82bff553 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/optional.ts @@ -0,0 +1,19 @@ +import type { Force } from '@traversable/registry' +import type { Returns } from '@traversable/registry' +import { symbol } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' + +type Nullable = Force + +export interface toJsonSchema { + (): Nullable> + [symbol.optional]: number +} + +export function toJsonSchema(optionalSchema: t.optional): toJsonSchema +export function toJsonSchema({ def }: t.optional) { + function optionalToJsonSchema() { return getSchema(def) } + optionalToJsonSchema[symbol.optional] = wrapOptional(def) + return optionalToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/record.ts b/packages/schema-to-json-schema/src/schemas/record.ts new file mode 100644 index 00000000..2503d568 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/record.ts @@ -0,0 +1,21 @@ +import type { t } from '@traversable/schema-core' +import type * as T from '@traversable/registry' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + type: 'object' + additionalProperties: T.Returns + } +} + +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { + return function recordToJsonSchema() { + return { + type: 'object' as const, + additionalProperties: getSchema(def), + } + } +} diff --git a/packages/schema-to-json-schema/src/schemas/string.ts b/packages/schema-to-json-schema/src/schemas/string.ts new file mode 100644 index 00000000..2956c069 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/string.ts @@ -0,0 +1,22 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { has } from '@traversable/registry' +import type { SizeBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): Force<{ type: 'string' } & PickIfDefined> +} + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.string): () => { type: 'string' } & Partial { + function stringToJsonSchema() { + const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null + const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null + let out: { type: 'string' } & Partial = { type: 'string' } + minLength !== null && void (out.minLength = minLength) + maxLength !== null && void (out.maxLength = maxLength) + + return out + } + return stringToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/symbol.ts b/packages/schema-to-json-schema/src/schemas/symbol.ts new file mode 100644 index 00000000..7046b08e --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/symbol.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function symbolToJsonSchema() { return void 0 } + return symbolToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/tuple.ts b/packages/schema-to-json-schema/src/schemas/tuple.ts new file mode 100644 index 00000000..ed4cf900 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/tuple.ts @@ -0,0 +1,37 @@ +import type { Returns } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' +import type { MinItems } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + type: 'array', + items: { [I in keyof T]: Returns } + additionalItems: false + minItems: MinItems + maxItems: T['length' & keyof T] + } +} + +export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema +export function toJsonSchema({ def }: t.tuple): () => { + type: 'array' + items: unknown + additionalItems: false + minItems?: {} + maxItems?: number +} { + function tupleToJsonSchema() { + let min = minItems(def) + let max = def.length + let items = applyTupleOptionality(def, { min, max }) + return { + type: 'array' as const, + additionalItems: false as const, + items, + minItems: min, + maxItems: max, + } + } + return tupleToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/undefined.ts b/packages/schema-to-json-schema/src/schemas/undefined.ts new file mode 100644 index 00000000..be46c306 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/undefined.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function undefinedToJsonSchema(): void { return void 0 } + return undefinedToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/union.ts b/packages/schema-to-json-schema/src/schemas/union.ts new file mode 100644 index 00000000..850f9f66 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/union.ts @@ -0,0 +1,17 @@ +import type { Returns } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { anyOf: { [I in keyof T]: Returns } } +} + +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema({ def }: t.union): () => {} { + return function unionToJsonSchema() { + return { + anyOf: def.map(getSchema) + } + } +} diff --git a/packages/schema-to-json-schema/src/schemas/unknown.ts b/packages/schema-to-json-schema/src/schemas/unknown.ts new file mode 100644 index 00000000..8d5be5a0 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/unknown.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return anyToJsonSchema +} diff --git a/packages/schema-to-json-schema/src/schemas/void.ts b/packages/schema-to-json-schema/src/schemas/void.ts new file mode 100644 index 00000000..d636b569 --- /dev/null +++ b/packages/schema-to-json-schema/src/schemas/void.ts @@ -0,0 +1,7 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function voidToJsonSchema(): void { + return void 0 + } + return voidToJsonSchema +} diff --git a/packages/schema-to-string/src/schemas/any.ts b/packages/schema-to-string/src/schemas/any.ts new file mode 100644 index 00000000..f70aa050 --- /dev/null +++ b/packages/schema-to-string/src/schemas/any.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'any' } +export function toString(): 'any' { return 'any' } diff --git a/packages/schema-to-string/src/schemas/array.ts b/packages/schema-to-string/src/schemas/array.ts new file mode 100644 index 00000000..5114ef0b --- /dev/null +++ b/packages/schema-to-string/src/schemas/array.ts @@ -0,0 +1,22 @@ +import type { t } from '@traversable/schema-core' + +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType})[]` +} + +export function toString(arraySchema: t.array): toString +export function toString(arraySchema: t.array): toString +export function toString({ def }: { def: unknown }) { + function arrayToString() { + let body = ( + !!def + && typeof def === 'object' + && 'toString' in def + && typeof def.toString === 'function' + ) ? def.toString() + : '${string}' + return ('(' + body + ')[]') + } + return arrayToString +} diff --git a/packages/schema-to-string/src/schemas/bigint.ts b/packages/schema-to-string/src/schemas/bigint.ts new file mode 100644 index 00000000..793c903e --- /dev/null +++ b/packages/schema-to-string/src/schemas/bigint.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'bigint' } +export function toString(): 'bigint' { return 'bigint' } diff --git a/packages/schema-to-string/src/schemas/boolean.ts b/packages/schema-to-string/src/schemas/boolean.ts new file mode 100644 index 00000000..3c408e57 --- /dev/null +++ b/packages/schema-to-string/src/schemas/boolean.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'boolean' } +export function toString(): 'boolean' { return 'boolean' } diff --git a/packages/schema-to-string/src/schemas/eq.ts b/packages/schema-to-string/src/schemas/eq.ts new file mode 100644 index 00000000..3c224bea --- /dev/null +++ b/packages/schema-to-string/src/schemas/eq.ts @@ -0,0 +1,17 @@ +import type { Key } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { stringify } from '@traversable/schema-to-string' + +export interface toString { + (): [Key] extends [never] + ? [T] extends [symbol] ? 'symbol' : 'symbol' + : [T] extends [string] ? `'${T}'` : Key +} + +export function toString(eqSchema: t.eq): toString +export function toString({ def }: t.eq): () => string { + function eqToString(): string { + return typeof def === 'symbol' ? 'symbol' : stringify(def) + } + return eqToString +} diff --git a/packages/schema-to-string/src/schemas/integer.ts b/packages/schema-to-string/src/schemas/integer.ts new file mode 100644 index 00000000..912565e6 --- /dev/null +++ b/packages/schema-to-string/src/schemas/integer.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } diff --git a/packages/schema-to-string/src/schemas/intersect.ts b/packages/schema-to-string/src/schemas/intersect.ts new file mode 100644 index 00000000..2e179159 --- /dev/null +++ b/packages/schema-to-string/src/schemas/intersect.ts @@ -0,0 +1,18 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + (): never | [T] extends [readonly []] ? 'unknown' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` +} + +export function toString(intersectSchema: t.intersect): toString +export function toString({ def }: t.intersect): () => string { + function intersectToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' + } + return intersectToString +} diff --git a/packages/schema-to-string/src/schemas/never.ts b/packages/schema-to-string/src/schemas/never.ts new file mode 100644 index 00000000..aaabf80d --- /dev/null +++ b/packages/schema-to-string/src/schemas/never.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'never' } +export function toString(): 'never' { return 'never' } diff --git a/packages/schema-to-string/src/schemas/null.ts b/packages/schema-to-string/src/schemas/null.ts new file mode 100644 index 00000000..35c3aef8 --- /dev/null +++ b/packages/schema-to-string/src/schemas/null.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'null' } +export function toString(): 'null' { return 'null' } diff --git a/packages/schema-to-string/src/schemas/number.ts b/packages/schema-to-string/src/schemas/number.ts new file mode 100644 index 00000000..912565e6 --- /dev/null +++ b/packages/schema-to-string/src/schemas/number.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } diff --git a/packages/schema-to-string/src/schemas/object.ts b/packages/schema-to-string/src/schemas/object.ts new file mode 100644 index 00000000..ad084ffa --- /dev/null +++ b/packages/schema-to-string/src/schemas/object.ts @@ -0,0 +1,41 @@ +import type { Join, UnionToTuple } from '@traversable/registry' +import { symbol } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +/** @internal */ +type Symbol_optional = typeof Symbol_optional +const Symbol_optional: typeof symbol.optional = symbol.optional + +/** @internal */ +const hasOptionalSymbol = (u: unknown): u is { toString(): T } => + !!u && typeof u === 'function' + && Symbol_optional in u + && typeof u[Symbol_optional] === 'number' + +/** @internal */ +const hasToString = (x: unknown): x is { toString(): string } => + !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' + +export interface toString> { + (): never + | [keyof T] extends [never] ? '{}' + /* @ts-expect-error */ + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` +} + +export function toString>(objectSchema: t.object): toString +export function toString({ def }: t.object) { + function objectToString() { + if (!!def && typeof def === 'object') { + const entries = Object.entries(def) + if (entries.length === 0) return '{}' + else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" + }: ${hasToString(x) ? x.toString() : 'unknown' + }`).join(', ') + } }` + } + else return '{ [x: string]: unknown }' + } + + return objectToString +} diff --git a/packages/schema-to-string/src/schemas/optional.ts b/packages/schema-to-string/src/schemas/optional.ts new file mode 100644 index 00000000..f4c96cc8 --- /dev/null +++ b/packages/schema-to-string/src/schemas/optional.ts @@ -0,0 +1,15 @@ +import type { t } from '@traversable/schema-core' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType} | undefined)` +} + +export function toString(optionalSchema: t.optional): toString +export function toString({ def }: t.optional): () => string { + function optionalToString(): string { + return '(' + callToString(def) + ' | undefined)' + } + return optionalToString +} diff --git a/packages/schema-to-string/src/schemas/record.ts b/packages/schema-to-string/src/schemas/record.ts new file mode 100644 index 00000000..868f3544 --- /dev/null +++ b/packages/schema-to-string/src/schemas/record.ts @@ -0,0 +1,17 @@ +import type { Returns } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + /* @ts-expect-error */ + (): never | `Record}>` +} + +export function toString>(recordSchema: S): toString +export function toString(recordSchema: t.record): toString +export function toString({ def }: { def: unknown }): () => string { + function recordToString() { + return `Record` + } + return recordToString +} diff --git a/packages/schema-to-string/src/schemas/string.ts b/packages/schema-to-string/src/schemas/string.ts new file mode 100644 index 00000000..86a98e16 --- /dev/null +++ b/packages/schema-to-string/src/schemas/string.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'string' } +export function toString(): 'string' { return 'string' } diff --git a/packages/schema-to-string/src/schemas/symbol.ts b/packages/schema-to-string/src/schemas/symbol.ts new file mode 100644 index 00000000..5651fe27 --- /dev/null +++ b/packages/schema-to-string/src/schemas/symbol.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'symbol' } +export function toString(): 'symbol' { return 'symbol' } diff --git a/packages/schema-to-string/src/schemas/tuple.ts b/packages/schema-to-string/src/schemas/tuple.ts new file mode 100644 index 00000000..b04c3381 --- /dev/null +++ b/packages/schema-to-string/src/schemas/tuple.ts @@ -0,0 +1,26 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import { hasToString } from '@traversable/schema-to-string' + +export interface toString { + (): never | `[${Join<{ + [I in keyof T]: `${ + /* @ts-expect-error */ + T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType + }` + }, ', '>}]` +} + +export function toString(tupleSchema: t.tuple): toString +export function toString(tupleSchema: t.tuple): () => string { + function stringToString() { + return Array_isArray(tupleSchema.def) + ? `[${tupleSchema.def.map( + (x) => t.optional.is(x) + ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` + : hasToString(x) ? x.toString() : 'unknown' + ).join(', ')}]` : 'unknown[]' + } + return stringToString +} diff --git a/packages/schema-to-string/src/schemas/undefined.ts b/packages/schema-to-string/src/schemas/undefined.ts new file mode 100644 index 00000000..a48b744b --- /dev/null +++ b/packages/schema-to-string/src/schemas/undefined.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'undefined' } +export function toString(): 'undefined' { return 'undefined' } diff --git a/packages/schema-to-string/src/schemas/union.ts b/packages/schema-to-string/src/schemas/union.ts new file mode 100644 index 00000000..6429d3e1 --- /dev/null +++ b/packages/schema-to-string/src/schemas/union.ts @@ -0,0 +1,18 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + (): never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` +} + +export function toString(unionSchema: t.union): toString +export function toString({ def }: t.union): () => string { + function unionToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' + } + return unionToString +} diff --git a/packages/schema-to-string/src/schemas/unknown.ts b/packages/schema-to-string/src/schemas/unknown.ts new file mode 100644 index 00000000..417e1048 --- /dev/null +++ b/packages/schema-to-string/src/schemas/unknown.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } diff --git a/packages/schema-to-string/src/schemas/void.ts b/packages/schema-to-string/src/schemas/void.ts new file mode 100644 index 00000000..487d08b3 --- /dev/null +++ b/packages/schema-to-string/src/schemas/void.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'void' } +export function toString(): 'void' { return 'void' } diff --git a/packages/schema/src/_exports.ts b/packages/schema/src/_exports.ts new file mode 100644 index 00000000..6cbf2955 --- /dev/null +++ b/packages/schema/src/_exports.ts @@ -0,0 +1,2 @@ +export * as t from './_namespace.js' +export { getConfig } from '@traversable/schema-core' diff --git a/packages/schema/src/_namespace.ts b/packages/schema/src/_namespace.ts new file mode 100644 index 00000000..bcd12d4f --- /dev/null +++ b/packages/schema/src/_namespace.ts @@ -0,0 +1,32 @@ +export type { + Entry, + FirstOptionalItem, + Guard, + Guarded, + invalid, + Schema, + SchemaLike, + TupleType, + ValidateTuple, +} from '@traversable/schema-core/namespace' + +export { any } from './schemas/any/core.js' +export { array } from './schemas/array/core.js' +export { bigint } from './schemas/bigint/core.js' +export { boolean } from './schemas/boolean/core.js' +export { eq } from './schemas/eq/core.js' +export { integer } from './schemas/integer/core.js' +export { intersect } from './schemas/intersect/core.js' +export { never } from './schemas/never/core.js' +export { null } from './schemas/null/core.js' +export { number } from './schemas/number/core.js' +export { object } from './schemas/object/core.js' +export { optional } from './schemas/optional/core.js' +export { record } from './schemas/record/core.js' +export { string } from './schemas/string/core.js' +export { symbol } from './schemas/symbol/core.js' +export { tuple } from './schemas/tuple/core.js' +export { undefined } from './schemas/undefined/core.js' +export { union } from './schemas/union/core.js' +export { unknown } from './schemas/unknown/core.js' +export { void } from './schemas/void/core.js' diff --git a/packages/schema/src/schemas/any/toJsonSchema.ts b/packages/schema/src/schemas/any/toJsonSchema.ts new file mode 100644 index 00000000..25336fc6 --- /dev/null +++ b/packages/schema/src/schemas/any/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return unknownToJsonSchema +} diff --git a/packages/schema/src/schemas/any/toString.ts b/packages/schema/src/schemas/any/toString.ts new file mode 100644 index 00000000..f70aa050 --- /dev/null +++ b/packages/schema/src/schemas/any/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'any' } +export function toString(): 'any' { return 'any' } diff --git a/packages/schema/src/schemas/any/validate.ts b/packages/schema/src/schemas/any/validate.ts new file mode 100644 index 00000000..a41facf6 --- /dev/null +++ b/packages/schema/src/schemas/any/validate.ts @@ -0,0 +1,10 @@ +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.unknown): validate { + validateUnknown.tag = URI.unknown + function validateUnknown() { return true as const } + return validateUnknown +} diff --git a/packages/schema/src/schemas/array/toJsonSchema.ts b/packages/schema/src/schemas/array/toJsonSchema.ts new file mode 100644 index 00000000..15462a4a --- /dev/null +++ b/packages/schema/src/schemas/array/toJsonSchema.ts @@ -0,0 +1,36 @@ +import type { t } from '../../_exports.js' +import type * as T from '@traversable/registry' +import type { SizeBounds } from '@traversable/schema-to-json-schema' +import { hasSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): never | T.Force< + & { type: 'array', items: T.Returns } + & T.PickIfDefined + > +} + +export function toJsonSchema>(arraySchema: T): toJsonSchema +export function toJsonSchema(arraySchema: T): toJsonSchema +export function toJsonSchema( + { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, +): () => { + type: 'array' + items: unknown + minLength?: number + maxLength?: number +} { + function arrayToJsonSchema() { + let items = hasSchema(def) ? def.toJsonSchema() : def + let out = { + type: 'array' as const, + items, + minLength, + maxLength, + } + if (typeof minLength !== 'number') delete out.minLength + if (typeof maxLength !== 'number') delete out.maxLength + return out + } + return arrayToJsonSchema +} diff --git a/packages/schema/src/schemas/array/toString.ts b/packages/schema/src/schemas/array/toString.ts new file mode 100644 index 00000000..fba36c7d --- /dev/null +++ b/packages/schema/src/schemas/array/toString.ts @@ -0,0 +1,22 @@ +import type { t } from '../../_exports.js' + +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType})[]` +} + +export function toString(arraySchema: t.array): toString +export function toString(arraySchema: t.array): toString +export function toString({ def }: { def: unknown }) { + function arrayToString() { + let body = ( + !!def + && typeof def === 'object' + && 'toString' in def + && typeof def.toString === 'function' + ) ? def.toString() + : '${string}' + return ('(' + body + ')[]') + } + return arrayToString +} diff --git a/packages/schema/src/schemas/array/validate.ts b/packages/schema/src/schemas/array/validate.ts new file mode 100644 index 00000000..6204c6d6 --- /dev/null +++ b/packages/schema/src/schemas/array/validate.ts @@ -0,0 +1,27 @@ +import { URI } from '@traversable/registry' +import type { t } from '../../_exports.js' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { Errors, NullaryErrors } from '@traversable/derive-validators' + +export type validate = never | ValidationFn +export function validate(arraySchema: t.array): validate +export function validate(arraySchema: t.array): validate +export function validate( + { def: { validate = () => true }, minLength, maxLength }: t.array +) { + validateArray.tag = URI.array + function validateArray(u: unknown, path = Array.of()) { + if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] + let errors = Array.of() + if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) + if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) + for (let i = 0, len = u.length; i < len; i++) { + let y = u[i] + let results = validate(y, [...path, i]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateArray +} diff --git a/packages/schema/src/schemas/bigint/toJsonSchema.ts b/packages/schema/src/schemas/bigint/toJsonSchema.ts new file mode 100644 index 00000000..f6c7bc5b --- /dev/null +++ b/packages/schema/src/schemas/bigint/toJsonSchema.ts @@ -0,0 +1,7 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function bigintToJsonSchema(): void { + return void 0 + } + return bigintToJsonSchema +} diff --git a/packages/schema/src/schemas/bigint/toString.ts b/packages/schema/src/schemas/bigint/toString.ts new file mode 100644 index 00000000..793c903e --- /dev/null +++ b/packages/schema/src/schemas/bigint/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'bigint' } +export function toString(): 'bigint' { return 'bigint' } diff --git a/packages/schema/src/schemas/bigint/validate.ts b/packages/schema/src/schemas/bigint/validate.ts new file mode 100644 index 00000000..4e563d80 --- /dev/null +++ b/packages/schema/src/schemas/bigint/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(bigIntSchema: S): validate { + validateBigInt.tag = URI.bigint + function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { + return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] + } + return validateBigInt +} diff --git a/packages/schema/src/schemas/boolean/toJsonSchema.ts b/packages/schema/src/schemas/boolean/toJsonSchema.ts new file mode 100644 index 00000000..d1b86f70 --- /dev/null +++ b/packages/schema/src/schemas/boolean/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'boolean' } } +export function toJsonSchema(): toJsonSchema { + function booleanToJsonSchema() { return { type: 'boolean' as const } } + return booleanToJsonSchema +} diff --git a/packages/schema/src/schemas/boolean/toString.ts b/packages/schema/src/schemas/boolean/toString.ts new file mode 100644 index 00000000..3c408e57 --- /dev/null +++ b/packages/schema/src/schemas/boolean/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'boolean' } +export function toString(): 'boolean' { return 'boolean' } diff --git a/packages/schema/src/schemas/boolean/validate.ts b/packages/schema/src/schemas/boolean/validate.ts new file mode 100644 index 00000000..89a3331b --- /dev/null +++ b/packages/schema/src/schemas/boolean/validate.ts @@ -0,0 +1,12 @@ +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(booleanSchema: t.boolean): validate { + validateBoolean.tag = URI.boolean + function validateBoolean(u: unknown, path = Array.of()) { + return booleanSchema(true as const) || [NullaryErrors.null(u, path)] + } + return validateBoolean +} diff --git a/packages/schema/src/schemas/eq/toJsonSchema.ts b/packages/schema/src/schemas/eq/toJsonSchema.ts new file mode 100644 index 00000000..f5c5c089 --- /dev/null +++ b/packages/schema/src/schemas/eq/toJsonSchema.ts @@ -0,0 +1,8 @@ +import type { t } from '../../_exports.js' + +export interface toJsonSchema { (): { const: T } } +export function toJsonSchema(eqSchema: t.eq): toJsonSchema +export function toJsonSchema({ def }: t.eq) { + function eqToJsonSchema() { return { const: def } } + return eqToJsonSchema +} diff --git a/packages/schema/src/schemas/eq/toString.ts b/packages/schema/src/schemas/eq/toString.ts new file mode 100644 index 00000000..7d8c8fb5 --- /dev/null +++ b/packages/schema/src/schemas/eq/toString.ts @@ -0,0 +1,17 @@ +import type { Key } from '@traversable/registry' +import type { t } from '../../_exports.js' +import { stringify } from '@traversable/schema-to-string' + +export interface toString { + (): [Key] extends [never] + ? [T] extends [symbol] ? 'symbol' : 'symbol' + : [T] extends [string] ? `'${T}'` : Key +} + +export function toString(eqSchema: t.eq): toString +export function toString({ def }: t.eq): () => string { + function eqToString(): string { + return typeof def === 'symbol' ? 'symbol' : stringify(def) + } + return eqToString +} diff --git a/packages/schema/src/schemas/eq/validate.ts b/packages/schema/src/schemas/eq/validate.ts new file mode 100644 index 00000000..91a59653 --- /dev/null +++ b/packages/schema/src/schemas/eq/validate.ts @@ -0,0 +1,17 @@ +import { Equal, getConfig, URI } from '@traversable/registry' +import type { t } from '../../_exports.js' +import type { Validate } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + +export type validate = Validate +export function validate(eqSchema: t.eq): validate +export function validate({ def }: t.eq): validate { + validateEq.tag = URI.eq + function validateEq(u: unknown, path = Array.of()) { + let options = getConfig().schema + let equals = options?.eq?.equalsFn || Equal.lax + if (equals(def, u)) return true + else return [Errors.eq(u, path, def)] + } + return validateEq +} diff --git a/packages/schema/src/schemas/integer/toJsonSchema.ts b/packages/schema/src/schemas/integer/toJsonSchema.ts new file mode 100644 index 00000000..1c2f6781 --- /dev/null +++ b/packages/schema/src/schemas/integer/toJsonSchema.ts @@ -0,0 +1,23 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '../../_exports.js' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.integer): toJsonSchema { + function integerToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'integer' as const, + ...bounds, + } + } + return integerToJsonSchema +} diff --git a/packages/schema/src/schemas/integer/toString.ts b/packages/schema/src/schemas/integer/toString.ts new file mode 100644 index 00000000..912565e6 --- /dev/null +++ b/packages/schema/src/schemas/integer/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } diff --git a/packages/schema/src/schemas/integer/validate.ts b/packages/schema/src/schemas/integer/validate.ts new file mode 100644 index 00000000..125ae847 --- /dev/null +++ b/packages/schema/src/schemas/integer/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(integerSchema: S): validate { + validateInteger.tag = URI.integer + function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { + return integerSchema(u) || [NullaryErrors.integer(u, path)] + } + return validateInteger +} diff --git a/packages/schema/src/schemas/intersect/toJsonSchema.ts b/packages/schema/src/schemas/intersect/toJsonSchema.ts new file mode 100644 index 00000000..85ea91f8 --- /dev/null +++ b/packages/schema/src/schemas/intersect/toJsonSchema.ts @@ -0,0 +1,20 @@ +import type { Returns } from '@traversable/registry' +import type { t } from '../../_exports.js' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + allOf: { [I in keyof T]: Returns } + } +} + +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema({ def }: t.intersect): () => {} { + function intersectToJsonSchema() { + return { + allOf: def.map(getSchema) + } + } + return intersectToJsonSchema +} diff --git a/packages/schema/src/schemas/intersect/toString.ts b/packages/schema/src/schemas/intersect/toString.ts new file mode 100644 index 00000000..9c23db18 --- /dev/null +++ b/packages/schema/src/schemas/intersect/toString.ts @@ -0,0 +1,18 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import type { t } from '../../_exports.js' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + (): never | [T] extends [readonly []] ? 'unknown' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` +} + +export function toString(intersectSchema: t.intersect): toString +export function toString({ def }: t.intersect): () => string { + function intersectToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' + } + return intersectToString +} diff --git a/packages/schema/src/schemas/intersect/validate.ts b/packages/schema/src/schemas/intersect/validate.ts new file mode 100644 index 00000000..330eb28f --- /dev/null +++ b/packages/schema/src/schemas/intersect/validate.ts @@ -0,0 +1,21 @@ +import { URI } from '@traversable/registry' +import type { t } from '../../_exports.js' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(intersectSchema: t.intersect): validate +export function validate(intersectSchema: t.intersect): validate +export function validate({ def }: t.intersect) { + validateIntersect.tag = URI.intersect + function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results !== true) + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + return errors.length === 0 || errors + } + return validateIntersect +} diff --git a/packages/schema/src/schemas/never/toJsonSchema.ts b/packages/schema/src/schemas/never/toJsonSchema.ts new file mode 100644 index 00000000..d22338df --- /dev/null +++ b/packages/schema/src/schemas/never/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): never } +export function toJsonSchema(): toJsonSchema { + function neverToJsonSchema() { return void 0 as never } + return neverToJsonSchema +} diff --git a/packages/schema/src/schemas/never/toString.ts b/packages/schema/src/schemas/never/toString.ts new file mode 100644 index 00000000..aaabf80d --- /dev/null +++ b/packages/schema/src/schemas/never/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'never' } +export function toString(): 'never' { return 'never' } diff --git a/packages/schema/src/schemas/never/validate.ts b/packages/schema/src/schemas/never/validate.ts new file mode 100644 index 00000000..b6658fa3 --- /dev/null +++ b/packages/schema/src/schemas/never/validate.ts @@ -0,0 +1,10 @@ +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.never): validate { + validateNever.tag = URI.never + function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } + return validateNever +} diff --git a/packages/schema/src/schemas/null/toJsonSchema.ts b/packages/schema/src/schemas/null/toJsonSchema.ts new file mode 100644 index 00000000..7a3b7c3a --- /dev/null +++ b/packages/schema/src/schemas/null/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'null', enum: [null] } } +export function toJsonSchema(): toJsonSchema { + function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } + return nullToJsonSchema +} diff --git a/packages/schema/src/schemas/null/toString.ts b/packages/schema/src/schemas/null/toString.ts new file mode 100644 index 00000000..35c3aef8 --- /dev/null +++ b/packages/schema/src/schemas/null/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'null' } +export function toString(): 'null' { return 'null' } diff --git a/packages/schema/src/schemas/null/validate.ts b/packages/schema/src/schemas/null/validate.ts new file mode 100644 index 00000000..0049e964 --- /dev/null +++ b/packages/schema/src/schemas/null/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(nullSchema: t.null): validate { + validateNull.tag = URI.null + function validateNull(u: unknown, path = Array.of()) { + return nullSchema(u) || [NullaryErrors.null(u, path)] + } + return validateNull +} diff --git a/packages/schema/src/schemas/number/toJsonSchema.ts b/packages/schema/src/schemas/number/toJsonSchema.ts new file mode 100644 index 00000000..7ca6d76e --- /dev/null +++ b/packages/schema/src/schemas/number/toJsonSchema.ts @@ -0,0 +1,23 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '../../_exports.js' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.number): toJsonSchema { + function numberToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'number' as const, + ...bounds, + } + } + return numberToJsonSchema +} diff --git a/packages/schema/src/schemas/number/toString.ts b/packages/schema/src/schemas/number/toString.ts new file mode 100644 index 00000000..912565e6 --- /dev/null +++ b/packages/schema/src/schemas/number/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } diff --git a/packages/schema/src/schemas/number/validate.ts b/packages/schema/src/schemas/number/validate.ts new file mode 100644 index 00000000..fbe5c398 --- /dev/null +++ b/packages/schema/src/schemas/number/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(numberSchema: S): validate { + validateNumber.tag = URI.number + function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return numberSchema(u) || [NullaryErrors.number(u, path)] + } + return validateNumber +} diff --git a/packages/schema/src/schemas/object/toJsonSchema.ts b/packages/schema/src/schemas/object/toJsonSchema.ts new file mode 100644 index 00000000..982b266a --- /dev/null +++ b/packages/schema/src/schemas/object/toJsonSchema.ts @@ -0,0 +1,27 @@ +import type { Returns } from '@traversable/registry' +import { fn, Object_keys } from '@traversable/registry' +import type { RequiredKeys } from '@traversable/schema-to-json-schema' +import { isRequired, property } from '@traversable/schema-to-json-schema' +import { t } from '../../_exports.js' + +export interface toJsonSchema = RequiredKeys> { + (): { + type: 'object' + required: { [I in keyof KS]: KS[I] & string } + properties: { [K in keyof T]: Returns } + } +} + +export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { + const required = Object_keys(def).filter(isRequired(def)) + function objectToJsonSchema() { + return { + type: 'object' as const, + required, + properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), + } + } + return objectToJsonSchema +} diff --git a/packages/schema/src/schemas/object/toString.ts b/packages/schema/src/schemas/object/toString.ts new file mode 100644 index 00000000..f9a6968b --- /dev/null +++ b/packages/schema/src/schemas/object/toString.ts @@ -0,0 +1,41 @@ +import type { Join, UnionToTuple } from '@traversable/registry' +import { symbol } from '@traversable/registry' +import { t } from '../../_exports.js' + +/** @internal */ +type Symbol_optional = typeof Symbol_optional +const Symbol_optional: typeof symbol.optional = symbol.optional + +/** @internal */ +const hasOptionalSymbol = (u: unknown): u is { toString(): T } => + !!u && typeof u === 'function' + && Symbol_optional in u + && typeof u[Symbol_optional] === 'number' + +/** @internal */ +const hasToString = (x: unknown): x is { toString(): string } => + !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' + +export interface toString> { + (): never + | [keyof T] extends [never] ? '{}' + /* @ts-expect-error */ + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` +} + +export function toString>(objectSchema: t.object): toString +export function toString({ def }: t.object) { + function objectToString() { + if (!!def && typeof def === 'object') { + const entries = Object.entries(def) + if (entries.length === 0) return '{}' + else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" + }: ${hasToString(x) ? x.toString() : 'unknown' + }`).join(', ') + } }` + } + else return '{ [x: string]: unknown }' + } + + return objectToString +} diff --git a/packages/schema/src/schemas/object/validate.ts b/packages/schema/src/schemas/object/validate.ts new file mode 100644 index 00000000..81cfe79d --- /dev/null +++ b/packages/schema/src/schemas/object/validate.ts @@ -0,0 +1,110 @@ +import { + Array_isArray, + has, + Object_keys, + Object_hasOwn, + typeName, + URI, +} from '@traversable/registry' +import type { t } from '../../_exports.js' +import { getConfig } from '../../_exports.js' +import type { ValidationError, Validator, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors, Errors, UnaryErrors } from '@traversable/derive-validators' + +/** @internal */ +let isObject = (u: unknown): u is { [x: string]: unknown } => + !!u && typeof u === 'object' && !Array_isArray(u) + +/** @internal */ +let isKeyOf = (k: keyof any, u: T): k is keyof T => + !!u && (typeof u === 'function' || typeof u === 'object') && k in u + +/** @internal */ +let isOptional = has('tag', (tag) => tag === URI.optional) + + +export type validate = never | ValidationFn + +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { + validateObject.tag = URI.object + function validateObject(u: unknown, path_ = Array.of()) { + // if (objectSchema(u)) return true + if (!isObject(u)) return [Errors.object(u, path_)] + let errors = Array.of() + let { schema: { optionalTreatment } } = getConfig() + let keys = Object_keys(objectSchema.def) + if (optionalTreatment === 'exactOptional') { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (Object_hasOwn(u, k) && u[k] === undefined) { + if (isOptional(objectSchema.def[k].validate)) { + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] + errors.push(NullaryErrors[tag](...args)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + errors.push(NullaryErrors[tag](u[k], path, tag)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag].invalid(u[k], path)) + } + errors.push(...results) + } + else if (Object_hasOwn(u, k)) { + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + errors.push(...results) + continue + } else { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + } + } + else { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (!Object_hasOwn(u, k)) { + if (!isOptional(objectSchema.def[k].validate)) { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + else { + if (!Object_hasOwn(u, k)) continue + if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { + if (u[k] === undefined) continue + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let j = 0; j < results.length; j++) { + let result = results[j] + errors.push(result) + continue + } + } + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let l = 0; l < results.length; l++) { + let result = results[l] + errors.push(result) + } + } + } + return errors.length === 0 || errors + } + + return validateObject +} diff --git a/packages/schema/src/schemas/optional/toJsonSchema.ts b/packages/schema/src/schemas/optional/toJsonSchema.ts new file mode 100644 index 00000000..922ec2b8 --- /dev/null +++ b/packages/schema/src/schemas/optional/toJsonSchema.ts @@ -0,0 +1,19 @@ +import type { Force } from '@traversable/registry' +import type { Returns } from '@traversable/registry' +import { symbol } from '@traversable/registry' +import type { t } from '../../_exports.js' +import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' + +type Nullable = Force + +export interface toJsonSchema { + (): Nullable> + [symbol.optional]: number +} + +export function toJsonSchema(optionalSchema: t.optional): toJsonSchema +export function toJsonSchema({ def }: t.optional) { + function optionalToJsonSchema() { return getSchema(def) } + optionalToJsonSchema[symbol.optional] = wrapOptional(def) + return optionalToJsonSchema +} diff --git a/packages/schema/src/schemas/optional/toString.ts b/packages/schema/src/schemas/optional/toString.ts new file mode 100644 index 00000000..f5d02852 --- /dev/null +++ b/packages/schema/src/schemas/optional/toString.ts @@ -0,0 +1,15 @@ +import type { t } from '../../_exports.js' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType} | undefined)` +} + +export function toString(optionalSchema: t.optional): toString +export function toString({ def }: t.optional): () => string { + function optionalToString(): string { + return '(' + callToString(def) + ' | undefined)' + } + return optionalToString +} diff --git a/packages/schema/src/schemas/optional/validate.ts b/packages/schema/src/schemas/optional/validate.ts new file mode 100644 index 00000000..bf4f366e --- /dev/null +++ b/packages/schema/src/schemas/optional/validate.ts @@ -0,0 +1,17 @@ +import { URI } from '@traversable/registry' +import { t } from '../../_exports.js' +import type { Validate, Validator, ValidationFn } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(optionalSchema: t.optional): validate +export function validate(optionalSchema: t.optional): validate +export function validate({ def }: t.optional): ValidationFn { + validateOptional.tag = URI.optional + validateOptional.optional = 1 + function validateOptional(u: unknown, path = Array.of()) { + if (u === void 0) return true + return def.validate(u, path) + } + return validateOptional +} diff --git a/packages/schema/src/schemas/record/toJsonSchema.ts b/packages/schema/src/schemas/record/toJsonSchema.ts new file mode 100644 index 00000000..f01343fe --- /dev/null +++ b/packages/schema/src/schemas/record/toJsonSchema.ts @@ -0,0 +1,21 @@ +import type { t } from '../../_exports.js' +import type * as T from '@traversable/registry' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + type: 'object' + additionalProperties: T.Returns + } +} + +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { + return function recordToJsonSchema() { + return { + type: 'object' as const, + additionalProperties: getSchema(def), + } + } +} diff --git a/packages/schema/src/schemas/record/toString.ts b/packages/schema/src/schemas/record/toString.ts new file mode 100644 index 00000000..1ef8dd01 --- /dev/null +++ b/packages/schema/src/schemas/record/toString.ts @@ -0,0 +1,17 @@ +import type { Returns } from '@traversable/registry' +import type { t } from '../../_exports.js' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + /* @ts-expect-error */ + (): never | `Record}>` +} + +export function toString>(recordSchema: S): toString +export function toString(recordSchema: t.record): toString +export function toString({ def }: { def: unknown }): () => string { + function recordToString() { + return `Record` + } + return recordToString +} diff --git a/packages/schema/src/schemas/record/validate.ts b/packages/schema/src/schemas/record/validate.ts new file mode 100644 index 00000000..10703a7f --- /dev/null +++ b/packages/schema/src/schemas/record/validate.ts @@ -0,0 +1,24 @@ +import type { t } from '../../_exports.js' +import { Array_isArray, Object_keys, URI } from '@traversable/registry' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = never | ValidationFn +export function validate(recordSchema: t.record): validate +export function validate(recordSchema: t.record): validate +export function validate({ def: { validate = () => true } }: t.record) { + validateRecord.tag = URI.record + function validateRecord(u: unknown, path = Array.of()) { + if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] + let errors = Array.of() + let keys = Object_keys(u) + for (let k of keys) { + let y = u[k] + let results = validate(y, [...path, k]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateRecord +} diff --git a/packages/schema/src/schemas/string/toJsonSchema.ts b/packages/schema/src/schemas/string/toJsonSchema.ts new file mode 100644 index 00000000..245236f5 --- /dev/null +++ b/packages/schema/src/schemas/string/toJsonSchema.ts @@ -0,0 +1,22 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '../../_exports.js' +import { has } from '@traversable/registry' +import type { SizeBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): Force<{ type: 'string' } & PickIfDefined> +} + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.string): () => { type: 'string' } & Partial { + function stringToJsonSchema() { + const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null + const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null + let out: { type: 'string' } & Partial = { type: 'string' } + minLength !== null && void (out.minLength = minLength) + maxLength !== null && void (out.maxLength = maxLength) + + return out + } + return stringToJsonSchema +} diff --git a/packages/schema/src/schemas/string/toString.ts b/packages/schema/src/schemas/string/toString.ts new file mode 100644 index 00000000..86a98e16 --- /dev/null +++ b/packages/schema/src/schemas/string/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'string' } +export function toString(): 'string' { return 'string' } diff --git a/packages/schema/src/schemas/string/validate.ts b/packages/schema/src/schemas/string/validate.ts new file mode 100644 index 00000000..22937e80 --- /dev/null +++ b/packages/schema/src/schemas/string/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(stringSchema: S): validate { + validateString.tag = URI.string + function validateString(u: unknown, path = Array.of()): true | ValidationError[] { + return stringSchema(u) || [NullaryErrors.number(u, path)] + } + return validateString +} diff --git a/packages/schema/src/schemas/symbol/toJsonSchema.ts b/packages/schema/src/schemas/symbol/toJsonSchema.ts new file mode 100644 index 00000000..7046b08e --- /dev/null +++ b/packages/schema/src/schemas/symbol/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function symbolToJsonSchema() { return void 0 } + return symbolToJsonSchema +} diff --git a/packages/schema/src/schemas/symbol/toString.ts b/packages/schema/src/schemas/symbol/toString.ts new file mode 100644 index 00000000..5651fe27 --- /dev/null +++ b/packages/schema/src/schemas/symbol/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'symbol' } +export function toString(): 'symbol' { return 'symbol' } diff --git a/packages/schema/src/schemas/symbol/validate.ts b/packages/schema/src/schemas/symbol/validate.ts new file mode 100644 index 00000000..e0c26452 --- /dev/null +++ b/packages/schema/src/schemas/symbol/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(symbolSchema: t.symbol): validate { + validateSymbol.tag = URI.symbol + function validateSymbol(u: unknown, path = Array.of()) { + return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] + } + return validateSymbol +} diff --git a/packages/schema/src/schemas/tuple/toJsonSchema.ts b/packages/schema/src/schemas/tuple/toJsonSchema.ts new file mode 100644 index 00000000..387c9d05 --- /dev/null +++ b/packages/schema/src/schemas/tuple/toJsonSchema.ts @@ -0,0 +1,37 @@ +import type { Returns } from '@traversable/registry' +import { t } from '../../_exports.js' +import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' +import type { MinItems } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + type: 'array', + items: { [I in keyof T]: Returns } + additionalItems: false + minItems: MinItems + maxItems: T['length' & keyof T] + } +} + +export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema +export function toJsonSchema({ def }: t.tuple): () => { + type: 'array' + items: unknown + additionalItems: false + minItems?: {} + maxItems?: number +} { + function tupleToJsonSchema() { + let min = minItems(def) + let max = def.length + let items = applyTupleOptionality(def, { min, max }) + return { + type: 'array' as const, + additionalItems: false as const, + items, + minItems: min, + maxItems: max, + } + } + return tupleToJsonSchema +} diff --git a/packages/schema/src/schemas/tuple/toString.ts b/packages/schema/src/schemas/tuple/toString.ts new file mode 100644 index 00000000..9e6d1eaf --- /dev/null +++ b/packages/schema/src/schemas/tuple/toString.ts @@ -0,0 +1,26 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import { t } from '../../_exports.js' +import { hasToString } from '@traversable/schema-to-string' + +export interface toString { + (): never | `[${Join<{ + [I in keyof T]: `${ + /* @ts-expect-error */ + T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType + }` + }, ', '>}]` +} + +export function toString(tupleSchema: t.tuple): toString +export function toString(tupleSchema: t.tuple): () => string { + function stringToString() { + return Array_isArray(tupleSchema.def) + ? `[${tupleSchema.def.map( + (x) => t.optional.is(x) + ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` + : hasToString(x) ? x.toString() : 'unknown' + ).join(', ')}]` : 'unknown[]' + } + return stringToString +} diff --git a/packages/schema/src/schemas/tuple/validate.ts b/packages/schema/src/schemas/tuple/validate.ts new file mode 100644 index 00000000..672f66e3 --- /dev/null +++ b/packages/schema/src/schemas/tuple/validate.ts @@ -0,0 +1,34 @@ +import { URI, Array_isArray } from '@traversable/registry' +import { t } from '../../_exports.js' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + +export type validate = Validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): Validate { + validateTuple.tag = URI.tuple + function validateTuple(u: unknown, path = Array.of()) { + let errors = Array.of() + if (!Array_isArray(u)) return [Errors.array(u, path)] + for (let i = 0; i < tupleSchema.def.length; i++) { + if (!(i in u) && !(t.optional.is(tupleSchema.def[i].validate))) { + errors.push(Errors.missingIndex(u, [...path, i])) + continue + } + let results = tupleSchema.def[i].validate(u[i], [...path, i]) + if (results !== true) { + for (let j = 0; j < results.length; j++) errors.push(results[j]) + results.push(Errors.arrayElement(u[i], [...path, i])) + } + } + if (u.length > tupleSchema.def.length) { + for (let k = tupleSchema.def.length; k < u.length; k++) { + let excess = u[k] + errors.push(Errors.excessItems(excess, [...path, k])) + } + } + return errors.length === 0 || errors + } + return validateTuple +} diff --git a/packages/schema/src/schemas/undefined/toJsonSchema.ts b/packages/schema/src/schemas/undefined/toJsonSchema.ts new file mode 100644 index 00000000..be46c306 --- /dev/null +++ b/packages/schema/src/schemas/undefined/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function undefinedToJsonSchema(): void { return void 0 } + return undefinedToJsonSchema +} diff --git a/packages/schema/src/schemas/undefined/toString.ts b/packages/schema/src/schemas/undefined/toString.ts new file mode 100644 index 00000000..a48b744b --- /dev/null +++ b/packages/schema/src/schemas/undefined/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'undefined' } +export function toString(): 'undefined' { return 'undefined' } diff --git a/packages/schema/src/schemas/undefined/validate.ts b/packages/schema/src/schemas/undefined/validate.ts new file mode 100644 index 00000000..8e0baa0a --- /dev/null +++ b/packages/schema/src/schemas/undefined/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(undefinedSchema: t.undefined): validate { + validateUndefined.tag = URI.undefined + function validateUndefined(u: unknown, path = Array.of()) { + return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] + } + return validateUndefined +} diff --git a/packages/schema/src/schemas/union/toJsonSchema.ts b/packages/schema/src/schemas/union/toJsonSchema.ts new file mode 100644 index 00000000..a8ec85b9 --- /dev/null +++ b/packages/schema/src/schemas/union/toJsonSchema.ts @@ -0,0 +1,17 @@ +import type { Returns } from '@traversable/registry' +import { t } from '../../_exports.js' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { anyOf: { [I in keyof T]: Returns } } +} + +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema({ def }: t.union): () => {} { + return function unionToJsonSchema() { + return { + anyOf: def.map(getSchema) + } + } +} diff --git a/packages/schema/src/schemas/union/toString.ts b/packages/schema/src/schemas/union/toString.ts new file mode 100644 index 00000000..2999cb50 --- /dev/null +++ b/packages/schema/src/schemas/union/toString.ts @@ -0,0 +1,18 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import { t } from '../../_exports.js' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + (): never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` +} + +export function toString(unionSchema: t.union): toString +export function toString({ def }: t.union): () => string { + function unionToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' + } + return unionToString +} diff --git a/packages/schema/src/schemas/union/validate.ts b/packages/schema/src/schemas/union/validate.ts new file mode 100644 index 00000000..a1658df2 --- /dev/null +++ b/packages/schema/src/schemas/union/validate.ts @@ -0,0 +1,26 @@ +import { URI } from '@traversable/registry' +import { t } from '../../_exports.js' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(unionSchema: t.union): validate +export function validate(unionSchema: t.union): validate +export function validate({ def }: t.union) { + validateUnion.tag = URI.union + function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { + // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results === true) { + // validateUnion.optional = 0 + return true + } + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + // validateUnion.optional = 0 + return errors.length === 0 || errors + } + return validateUnion +} diff --git a/packages/schema/src/schemas/unknown/toJsonSchema.ts b/packages/schema/src/schemas/unknown/toJsonSchema.ts new file mode 100644 index 00000000..8d5be5a0 --- /dev/null +++ b/packages/schema/src/schemas/unknown/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return anyToJsonSchema +} diff --git a/packages/schema/src/schemas/unknown/toString.ts b/packages/schema/src/schemas/unknown/toString.ts new file mode 100644 index 00000000..417e1048 --- /dev/null +++ b/packages/schema/src/schemas/unknown/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } diff --git a/packages/schema/src/schemas/unknown/validate.ts b/packages/schema/src/schemas/unknown/validate.ts new file mode 100644 index 00000000..3acaa008 --- /dev/null +++ b/packages/schema/src/schemas/unknown/validate.ts @@ -0,0 +1,10 @@ +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.any): validate { + validateAny.tag = URI.any + function validateAny() { return true as const } + return validateAny +} diff --git a/packages/schema/src/schemas/void/toJsonSchema.ts b/packages/schema/src/schemas/void/toJsonSchema.ts new file mode 100644 index 00000000..d636b569 --- /dev/null +++ b/packages/schema/src/schemas/void/toJsonSchema.ts @@ -0,0 +1,7 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function voidToJsonSchema(): void { + return void 0 + } + return voidToJsonSchema +} diff --git a/packages/schema/src/schemas/void/toString.ts b/packages/schema/src/schemas/void/toString.ts new file mode 100644 index 00000000..487d08b3 --- /dev/null +++ b/packages/schema/src/schemas/void/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'void' } +export function toString(): 'void' { return 'void' } diff --git a/packages/schema/src/schemas/void/validate.ts b/packages/schema/src/schemas/void/validate.ts new file mode 100644 index 00000000..68261f9a --- /dev/null +++ b/packages/schema/src/schemas/void/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '../../_exports.js' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(voidSchema: t.void): validate { + validateVoid.tag = URI.void + function validateVoid(u: unknown, path = Array.of()) { + return voidSchema(u) || [NullaryErrors.void(u, path)] + } + return validateVoid +} From 303ab80b6e10b477821d2f75645aa63dde682c31 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sun, 13 Apr 2025 09:17:31 -0500 Subject: [PATCH 31/45] feat(generator): generators generate --- packages/derive-equals/package.json | 3 +- packages/derive-equals/src/schemas/bigint.ts | 4 +- packages/derive-equals/src/schemas/boolean.ts | 4 +- packages/derive-equals/src/schemas/integer.ts | 2 +- packages/derive-equals/src/schemas/never.ts | 2 +- packages/derive-equals/src/schemas/null.ts | 4 +- packages/derive-equals/src/schemas/number.ts | 2 +- packages/derive-equals/src/schemas/symbol.ts | 4 +- .../derive-equals/src/schemas/undefined.ts | 4 +- packages/derive-equals/src/schemas/unknown.ts | 4 +- packages/derive-equals/src/schemas/void.ts | 4 +- packages/registry/src/pick.ts | 1 + packages/registry/src/types.ts | 1 + .../test/test-data/any/toString.ts | 4 +- .../test/test-data/array/toString.ts | 2 +- .../test/test-data/unknown/toString.ts | 4 +- packages/schema-to-json-schema/package.json | 3 +- packages/schema/core.ts | 36 -- packages/schema/package.json | 2 + packages/schema/src/__schemas__/any.ts | 36 -- packages/schema/src/__schemas__/array.ts | 128 ----- packages/schema/src/__schemas__/bigint.ts | 99 ---- packages/schema/src/__schemas__/boolean.ts | 37 -- packages/schema/src/__schemas__/eq.ts | 41 -- packages/schema/src/__schemas__/integer.ts | 100 ---- packages/schema/src/__schemas__/intersect.ts | 50 -- packages/schema/src/__schemas__/never.ts | 37 -- packages/schema/src/__schemas__/null.ts | 39 -- packages/schema/src/__schemas__/number.ts | 139 ----- packages/schema/src/__schemas__/object.ts | 79 --- packages/schema/src/__schemas__/of.ts | 44 -- packages/schema/src/__schemas__/optional.ts | 55 -- packages/schema/src/__schemas__/record.ts | 50 -- packages/schema/src/__schemas__/string.ts | 102 ---- packages/schema/src/__schemas__/symbol.ts | 36 -- packages/schema/src/__schemas__/tuple.ts | 86 --- packages/schema/src/__schemas__/undefined.ts | 36 -- packages/schema/src/__schemas__/union.ts | 50 -- packages/schema/src/__schemas__/unknown.ts | 36 -- packages/schema/src/__schemas__/void.ts | 36 -- packages/schema/src/build.ts | 503 +++++++++++++----- packages/schema/src/schemas/array/core.ts | 4 +- packages/schema/src/schemas/array/equals.ts | 2 +- packages/schema/src/schemas/bigint/equals.ts | 4 +- packages/schema/src/schemas/boolean/equals.ts | 4 +- packages/schema/src/schemas/eq/equals.ts | 2 +- packages/schema/src/schemas/integer/equals.ts | 2 +- packages/schema/src/schemas/intersect/core.ts | 2 +- .../schema/src/schemas/intersect/equals.ts | 2 +- packages/schema/src/schemas/never/equals.ts | 2 +- packages/schema/src/schemas/null/equals.ts | 4 +- packages/schema/src/schemas/number/equals.ts | 2 +- packages/schema/src/schemas/object/core.ts | 2 +- packages/schema/src/schemas/object/equals.ts | 2 +- packages/schema/src/schemas/of/core.ts | 2 +- packages/schema/src/schemas/optional/core.ts | 2 +- .../schema/src/schemas/optional/equals.ts | 2 +- packages/schema/src/schemas/record/core.ts | 2 +- packages/schema/src/schemas/record/equals.ts | 2 +- packages/schema/src/schemas/symbol/equals.ts | 4 +- packages/schema/src/schemas/tuple/core.ts | 4 +- packages/schema/src/schemas/tuple/equals.ts | 2 +- .../schema/src/schemas/undefined/equals.ts | 4 +- packages/schema/src/schemas/union/core.ts | 2 +- packages/schema/src/schemas/union/equals.ts | 2 +- packages/schema/src/schemas/unknown/equals.ts | 4 +- packages/schema/src/schemas/void/equals.ts | 4 +- packages/schema/tsconfig.build.json | 1 + packages/schema/tsconfig.src.json | 1 + packages/schema/tsconfig.test.json | 1 + pnpm-lock.yaml | 3 + 71 files changed, 454 insertions(+), 1531 deletions(-) delete mode 100644 packages/schema/core.ts delete mode 100644 packages/schema/src/__schemas__/any.ts delete mode 100644 packages/schema/src/__schemas__/array.ts delete mode 100644 packages/schema/src/__schemas__/bigint.ts delete mode 100644 packages/schema/src/__schemas__/boolean.ts delete mode 100644 packages/schema/src/__schemas__/eq.ts delete mode 100644 packages/schema/src/__schemas__/integer.ts delete mode 100644 packages/schema/src/__schemas__/intersect.ts delete mode 100644 packages/schema/src/__schemas__/never.ts delete mode 100644 packages/schema/src/__schemas__/null.ts delete mode 100644 packages/schema/src/__schemas__/number.ts delete mode 100644 packages/schema/src/__schemas__/object.ts delete mode 100644 packages/schema/src/__schemas__/of.ts delete mode 100644 packages/schema/src/__schemas__/optional.ts delete mode 100644 packages/schema/src/__schemas__/record.ts delete mode 100644 packages/schema/src/__schemas__/string.ts delete mode 100644 packages/schema/src/__schemas__/symbol.ts delete mode 100644 packages/schema/src/__schemas__/tuple.ts delete mode 100644 packages/schema/src/__schemas__/undefined.ts delete mode 100644 packages/schema/src/__schemas__/union.ts delete mode 100644 packages/schema/src/__schemas__/unknown.ts delete mode 100644 packages/schema/src/__schemas__/void.ts diff --git a/packages/derive-equals/package.json b/packages/derive-equals/package.json index 89bea69c..342235ff 100644 --- a/packages/derive-equals/package.json +++ b/packages/derive-equals/package.json @@ -17,7 +17,8 @@ "@traversable": { "generateExports": { "include": [ - "**/*.ts" + "**/*.ts", + "schemas/*.ts" ] }, "generateIndex": { diff --git a/packages/derive-equals/src/schemas/bigint.ts b/packages/derive-equals/src/schemas/bigint.ts index 3f38a8a5..7c98bbf1 100644 --- a/packages/derive-equals/src/schemas/bigint.ts +++ b/packages/derive-equals/src/schemas/bigint.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: bigint, right: bigint): boolean { diff --git a/packages/derive-equals/src/schemas/boolean.ts b/packages/derive-equals/src/schemas/boolean.ts index c060588a..306bb12b 100644 --- a/packages/derive-equals/src/schemas/boolean.ts +++ b/packages/derive-equals/src/schemas/boolean.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: boolean, right: boolean): boolean { diff --git a/packages/derive-equals/src/schemas/integer.ts b/packages/derive-equals/src/schemas/integer.ts index a599f588..29fcd602 100644 --- a/packages/derive-equals/src/schemas/integer.ts +++ b/packages/derive-equals/src/schemas/integer.ts @@ -1,5 +1,5 @@ import type { Equal } from '@traversable/registry' -import { SameValueNumber } from "@traversable/registry" +import { SameValueNumber } from '@traversable/registry' export type equals = Equal export function equals(left: number, right: number): boolean { diff --git a/packages/derive-equals/src/schemas/never.ts b/packages/derive-equals/src/schemas/never.ts index 7348de1a..3ed89421 100644 --- a/packages/derive-equals/src/schemas/never.ts +++ b/packages/derive-equals/src/schemas/never.ts @@ -1,4 +1,4 @@ -import type { Equal } from "@traversable/registry" +import type { Equal } from '@traversable/registry' export type equals = Equal export function equals(left: never, right: never): boolean { diff --git a/packages/derive-equals/src/schemas/null.ts b/packages/derive-equals/src/schemas/null.ts index fe92c107..12c2f636 100644 --- a/packages/derive-equals/src/schemas/null.ts +++ b/packages/derive-equals/src/schemas/null.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: null, right: null): boolean { diff --git a/packages/derive-equals/src/schemas/number.ts b/packages/derive-equals/src/schemas/number.ts index a599f588..29fcd602 100644 --- a/packages/derive-equals/src/schemas/number.ts +++ b/packages/derive-equals/src/schemas/number.ts @@ -1,5 +1,5 @@ import type { Equal } from '@traversable/registry' -import { SameValueNumber } from "@traversable/registry" +import { SameValueNumber } from '@traversable/registry' export type equals = Equal export function equals(left: number, right: number): boolean { diff --git a/packages/derive-equals/src/schemas/symbol.ts b/packages/derive-equals/src/schemas/symbol.ts index 24e82beb..f3bb7486 100644 --- a/packages/derive-equals/src/schemas/symbol.ts +++ b/packages/derive-equals/src/schemas/symbol.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: symbol, right: symbol): boolean { diff --git a/packages/derive-equals/src/schemas/undefined.ts b/packages/derive-equals/src/schemas/undefined.ts index 2836cd51..75156d56 100644 --- a/packages/derive-equals/src/schemas/undefined.ts +++ b/packages/derive-equals/src/schemas/undefined.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: undefined, right: undefined): boolean { diff --git a/packages/derive-equals/src/schemas/unknown.ts b/packages/derive-equals/src/schemas/unknown.ts index 41b30fd5..ccd2a780 100644 --- a/packages/derive-equals/src/schemas/unknown.ts +++ b/packages/derive-equals/src/schemas/unknown.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: any, right: any): boolean { diff --git a/packages/derive-equals/src/schemas/void.ts b/packages/derive-equals/src/schemas/void.ts index 6f7b9779..d11d89e3 100644 --- a/packages/derive-equals/src/schemas/void.ts +++ b/packages/derive-equals/src/schemas/void.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: void, right: void): boolean { diff --git a/packages/registry/src/pick.ts b/packages/registry/src/pick.ts index a6e68123..58de5cab 100644 --- a/packages/registry/src/pick.ts +++ b/packages/registry/src/pick.ts @@ -31,6 +31,7 @@ export type omit = never | { [P in keyof T as P extends K export declare namespace omit { type Lax = never | { [P in keyof T as P extends K ? never : P]: T[P] } type Where = never | { [K in keyof T as T[K] extends S ? never : K]: T[K] } + type When = never | { [K in keyof T as T[K] extends S | undefined ? never : K]: T[K] } type List = never | { [I in keyof T as I extends keyof [] | K | Key ? never : I]: T[I] } type Any = [T] extends [readonly unknown[]] ? omit.List : omit type NonFiniteObject = [string] extends [K] ? T : omit.Lax> diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index ea842bd9..64c2549f 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -39,6 +39,7 @@ export type PickIfDefined< export type Param = T extends (_: infer I) => unknown ? I : never export type Parameters = T extends (..._: infer I) => unknown ? I : never export type Returns = T extends (_: never) => infer O ? O : never +export type IfReturns = T extends (_: never) => infer O ? O : T export type Conform = Extract> = [_] extends [never] ? Extract : _ export type Target = S extends (_: any) => _ is infer T ? T : never diff --git a/packages/schema-generator/test/test-data/any/toString.ts b/packages/schema-generator/test/test-data/any/toString.ts index 417e1048..f70aa050 100644 --- a/packages/schema-generator/test/test-data/any/toString.ts +++ b/packages/schema-generator/test/test-data/any/toString.ts @@ -1,2 +1,2 @@ -export interface toString { (): 'unknown' } -export function toString(): 'unknown' { return 'unknown' } +export interface toString { (): 'any' } +export function toString(): 'any' { return 'any' } diff --git a/packages/schema-generator/test/test-data/array/toString.ts b/packages/schema-generator/test/test-data/array/toString.ts index 58eee17d..5114ef0b 100644 --- a/packages/schema-generator/test/test-data/array/toString.ts +++ b/packages/schema-generator/test/test-data/array/toString.ts @@ -1,4 +1,4 @@ -import { t } from '@traversable/schema-core' +import type { t } from '@traversable/schema-core' export interface toString { /* @ts-expect-error */ diff --git a/packages/schema-generator/test/test-data/unknown/toString.ts b/packages/schema-generator/test/test-data/unknown/toString.ts index f70aa050..417e1048 100644 --- a/packages/schema-generator/test/test-data/unknown/toString.ts +++ b/packages/schema-generator/test/test-data/unknown/toString.ts @@ -1,2 +1,2 @@ -export interface toString { (): 'any' } -export function toString(): 'any' { return 'any' } +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } diff --git a/packages/schema-to-json-schema/package.json b/packages/schema-to-json-schema/package.json index 3d2f61a7..40ef0290 100644 --- a/packages/schema-to-json-schema/package.json +++ b/packages/schema-to-json-schema/package.json @@ -20,7 +20,8 @@ "@traversable": { "generateExports": { "include": [ - "**/*.ts" + "**/*.ts", + "schemas/*.ts" ] }, "generateIndex": { diff --git a/packages/schema/core.ts b/packages/schema/core.ts deleted file mode 100644 index d178213e..00000000 --- a/packages/schema/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { void_ as void, void_ } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface void_ extends void_.core { - //<%= Types %> -} - -function VoidSchema(src: unknown): src is void { return src === void 0 } -VoidSchema.tag = URI.void -VoidSchema.def = void 0 as void - -const void_ = Object_assign( - VoidSchema, - userDefinitions, -) as void_ - -Object_assign(void_, userExtensions) - -declare namespace void_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.void - _type: void - get def(): this['_type'] - } -} diff --git a/packages/schema/package.json b/packages/schema/package.json index beb4b9a9..df63c421 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -40,6 +40,7 @@ "peerDependencies": { "@traversable/derive-codec": "workspace:^", "@traversable/derive-equals": "workspace:^", + "@traversable/derive-validators": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema-core": "workspace:^", "@traversable/schema-generator": "workspace:^", @@ -49,6 +50,7 @@ "devDependencies": { "@traversable/derive-codec": "workspace:^", "@traversable/derive-equals": "workspace:^", + "@traversable/derive-validators": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema-core": "workspace:^", "@traversable/schema-generator": "workspace:^", diff --git a/packages/schema/src/__schemas__/any.ts b/packages/schema/src/__schemas__/any.ts deleted file mode 100644 index 877f92af..00000000 --- a/packages/schema/src/__schemas__/any.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { any_ as any } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface any_ extends any_.core { - //<%= Types %> -} - -function AnySchema(src: unknown): src is any { return true } -AnySchema.tag = URI.any -AnySchema.def = void 0 as any - -const any_ = Object_assign( - AnySchema, - userDefinitions, -) as any_ - -Object_assign(any_, userExtensions) - -declare namespace any_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.any - _type: any - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/array.ts b/packages/schema/src/__schemas__/array.ts deleted file mode 100644 index 0e73fc5f..00000000 --- a/packages/schema/src/__schemas__/array.ts +++ /dev/null @@ -1,128 +0,0 @@ -import type { - Bounds, - Integer, - Unknown, -} from '@traversable/registry' -import { - Array_isArray, - array as arrayOf, - bindUserExtensions, - carryover, - within, - _isPredicate, - has, - Math_max, - Math_min, - Number_isSafeInteger, - Object_assign, - URI, -} from '@traversable/registry' - -import type { Guarded, Schema, SchemaLike } from '@traversable/schema-core/namespace' - -import type { of } from './of.js' - -/** @internal */ -function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { - return Object_assign(function BoundedArraySchema(u: unknown) { - return Array_isArray(u) && within(bounds)(u.length) - }, carry, array(schema)) -} - -export interface array extends array.core { - //<%= Types %> -} - -export function array(schema: S, readonly: 'readonly'): readonlyArray -export function array(schema: S): array -export function array(schema: S): array>> -export function array(schema: S): array { - return array.def(schema) -} - -export namespace array { - export let userDefinitions: Record = { - //<%= Definitions %> - } as array - export function def(x: S, prev?: array): array - export function def(x: S, prev?: unknown): array - export function def(x: S, prev?: array): array - /* v8 ignore next 1 */ - export function def(x: unknown, prev?: unknown): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray - function ArraySchema(src: unknown) { return predicate(src) } - ArraySchema.tag = URI.array - ArraySchema.def = x - ArraySchema.min = function arrayMin(minLength: Min) { - return Object_assign( - boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), - { minLength }, - ) - } - ArraySchema.max = function arrayMax(maxLength: Max) { - return Object_assign( - boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), - { maxLength }, - ) - } - ArraySchema.between = function arrayBetween( - min: Min, - max: Max, - minLength = Math_min(min, max), - maxLength = Math_max(min, max) - ) { - return Object_assign( - boundedArray(x, { gte: minLength, lte: maxLength }), - { minLength, maxLength }, - ) - } - if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength - if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength - Object_assign(ArraySchema, userDefinitions) - return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) - } -} - -export declare namespace array { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.array - get def(): S - _type: S['_type' & keyof S][] - minLength?: number - maxLength?: number - min>(minLength: Min): array.Min - max>(maxLength: Max): array.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> - } - type Min - = [Self] extends [{ maxLength: number }] - ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> - : array.min - ; - type Max - = [Self] extends [{ minLength: number }] - ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> - : array.max - ; - interface min extends array { minLength: Min } - interface max extends array { maxLength: Max } - interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } - type type = never | T -} - -export const readonlyArray: { - (schema: S): readonlyArray - (schema: S): readonlyArray> -} = array -export interface readonlyArray { - (u: unknown): u is this['_type'] - tag: URI.array - def: S - _type: ReadonlyArray -} diff --git a/packages/schema/src/__schemas__/bigint.ts b/packages/schema/src/__schemas__/bigint.ts deleted file mode 100644 index f7d55261..00000000 --- a/packages/schema/src/__schemas__/bigint.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { Bounds, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Object_assign, - URI, - withinBig as within, -} from '@traversable/registry' - -export { bigint_ as bigint } - -/** @internal */ -function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ -function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ -function boundedBigInt(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedBigIntSchema(u: unknown) { - return bigint_(u) && within(bounds)(u) - }, carry, bigint_) -} - -interface bigint_ extends bigint_.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function BigIntSchema(src: unknown) { return typeof src === 'bigint' } -BigIntSchema.tag = URI.bigint -BigIntSchema.def = 0n - -const bigint_ = Object_assign( - BigIntSchema, - userDefinitions, -) as bigint_ - -bigint_.min = function bigIntMin(minimum) { - return Object_assign( - boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -bigint_.max = function bigIntMax(maximum) { - return Object_assign( - boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -bigint_.between = function bigIntBetween( - min, - max, - minimum = (max < min ? max : min), - maximum = (max < min ? min : max), -) { - return Object_assign( - boundedBigInt({ gte: minimum, lte: maximum }), - { minimum, maximum } - ) -} - -Object_assign( - bigint_, - bindUserExtensions(bigint_, userExtensions), -) - -declare namespace bigint_ { - interface core extends bigint_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: bigint - tag: URI.bigint - get def(): this['_type'] - minimum?: bigint - maximum?: bigint - } - type Min - = [Self] extends [{ maximum: bigint }] - ? bigint_.between<[min: X, max: Self['maximum']]> - : bigint_.min - ; - type Max - = [Self] extends [{ minimum: bigint }] - ? bigint_.between<[min: Self['minimum'], max: X]> - : bigint_.max - ; - interface methods { - min(minimum: Min): bigint_.Min - max(maximum: Max): bigint_.Max - between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> - } - interface min extends bigint_ { minimum: Min } - interface max extends bigint_ { maximum: Max } - interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } -} - diff --git a/packages/schema/src/__schemas__/boolean.ts b/packages/schema/src/__schemas__/boolean.ts deleted file mode 100644 index c89adf4b..00000000 --- a/packages/schema/src/__schemas__/boolean.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { boolean_ as boolean } - -interface boolean_ extends boolean_.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } - -BooleanSchema.tag = URI.boolean -BooleanSchema.def = false - -const boolean_ = Object_assign( - BooleanSchema, - userDefinitions, -) as boolean_ - -Object_assign(boolean_, userExtensions) - -declare namespace boolean_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.boolean - _type: boolean - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/eq.ts b/packages/schema/src/__schemas__/eq.ts deleted file mode 100644 index d701a0f6..00000000 --- a/packages/schema/src/__schemas__/eq.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' -import { applyOptions, bindUserExtensions, _isPredicate, Object_assign, URI } from '@traversable/registry' - -export function eq>(value: V, options?: Options): eq> -export function eq(value: V, options?: Options): eq -export function eq(value: V, options?: Options): eq { - return eq.def(value, options) -} - -export interface eq extends eq.core { - //<%= Types %> -} - -export namespace eq { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(value: T, options?: Options): eq - /* v8 ignore next 1 */ - export function def(x: T, $?: Options): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const options = applyOptions($) - const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) - function EqSchema(src: unknown) { return predicate(src) } - EqSchema.tag = URI.eq - EqSchema.def = x - Object_assign(EqSchema, eq.userDefinitions) - return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) - } -} - -export declare namespace eq { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.eq - _type: V - get def(): V - } -} diff --git a/packages/schema/src/__schemas__/integer.ts b/packages/schema/src/__schemas__/integer.ts deleted file mode 100644 index 2969e5a1..00000000 --- a/packages/schema/src/__schemas__/integer.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { Bounds, Integer, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_min, - Math_max, - Number_isSafeInteger, - Object_assign, - URI, - within, -} from '@traversable/registry' - - -export { integer } - -/** @internal */ -function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer -function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer -function boundedInteger(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedIntegerSchema(u: unknown) { - return integer(u) && within(bounds)(u) - }, carry, integer) -} - -interface integer extends integer.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } -IntegerSchema.tag = URI.integer -IntegerSchema.def = 0 - -const integer = Object_assign( - IntegerSchema, - userDefinitions, -) as integer - -integer.min = function integerMin(minimum) { - return Object_assign( - boundedInteger({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -integer.max = function integerMax(maximum) { - return Object_assign( - boundedInteger({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -integer.between = function integerBetween( - min, - max, - minimum = Math_min(min, max), - maximum = Math_max(min, max), -) { - return Object_assign( - boundedInteger({ gte: minimum, lte: maximum }), - { minimum, maximum }, - ) -} - -Object_assign( - integer, - bindUserExtensions(integer, userExtensions), -) - -declare namespace integer { - interface core extends integer.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: number - tag: URI.integer - get def(): this['_type'] - minimum?: number - maximum?: number - } - interface methods { - min>(minimum: Min): integer.Min - max>(maximum: Max): integer.Max - between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ maximum: number }] - ? integer.between<[min: X, max: Self['maximum']]> - : integer.min - type Max - = [Self] extends [{ minimum: number }] - ? integer.between<[min: Self['minimum'], max: X]> - : integer.max - interface min extends integer { minimum: Min } - interface max extends integer { maximum: Max } - interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } -} diff --git a/packages/schema/src/__schemas__/intersect.ts b/packages/schema/src/__schemas__/intersect.ts deleted file mode 100644 index 916aa265..00000000 --- a/packages/schema/src/__schemas__/intersect.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - _isPredicate, - bindUserExtensions, - intersect as intersect$, - isUnknown as isAny, - Object_assign, - URI, -} from '@traversable/registry' - -import type { Entry, IntersectType, Schema, SchemaLike } from '@traversable/schema-core/namespace' - -export function intersect(...schemas: S): intersect -export function intersect }>(...schemas: S): intersect -export function intersect(...schemas: readonly unknown[]) { - return intersect.def(schemas) -} - -export interface intersect extends intersect.core { - //<%= Types %> -} - -export namespace intersect { - export let userDefinitions: Record = { - //<%= Definitions %> - } as intersect - export function def(xs: readonly [...T]): intersect - /* v8 ignore next 1 */ - export function def(xs: readonly unknown[]): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny - function IntersectSchema(src: unknown) { return predicate(src) } - IntersectSchema.tag = URI.intersect - IntersectSchema.def = xs - Object_assign(IntersectSchema, intersect.userDefinitions) - return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) - } -} - -export declare namespace intersect { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.intersect - get def(): S - _type: IntersectType - } - type type> = never | T -} diff --git a/packages/schema/src/__schemas__/never.ts b/packages/schema/src/__schemas__/never.ts deleted file mode 100644 index a0077281..00000000 --- a/packages/schema/src/__schemas__/never.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { never_ as never } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface never_ extends never_.core { - //<%= Types %> -} - -function NeverSchema(src: unknown): src is never { return false } -NeverSchema.tag = URI.never; -NeverSchema.def = void 0 as never - -const never_ = Object_assign( - NeverSchema, - userDefinitions, -) as never_ - -Object_assign(never_, userExtensions) - -export declare namespace never_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.never - _type: never - get def(): this['_type'] - } -} - diff --git a/packages/schema/src/__schemas__/null.ts b/packages/schema/src/__schemas__/null.ts deleted file mode 100644 index befebeb5..00000000 --- a/packages/schema/src/__schemas__/null.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { null_ as null, null_ } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface null_ extends null_.core { - //<%= Types %> -} - -function NullSchema(src: unknown): src is null { return src === null } -NullSchema.def = null -NullSchema.tag = URI.null - -const null_ = Object_assign( - NullSchema, - userDefinitions, -) as null_ - -Object_assign( - null_, - userExtensions, -) - -declare namespace null_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.null - _type: null - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/number.ts b/packages/schema/src/__schemas__/number.ts deleted file mode 100644 index 5972ae85..00000000 --- a/packages/schema/src/__schemas__/number.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { Bounds, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_min, - Math_max, - Object_assign, - URI, - within, -} from '@traversable/registry' - - -export { number_ as number } - -interface number_ extends number_.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function NumberSchema(src: unknown) { return typeof src === 'number' } -NumberSchema.tag = URI.number -NumberSchema.def = 0 - -const number_ = Object_assign( - NumberSchema, - userDefinitions, -) as number_ - -number_.min = function numberMin(minimum) { - return Object_assign( - boundedNumber({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -number_.max = function numberMax(maximum) { - return Object_assign( - boundedNumber({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -number_.moreThan = function numberMoreThan(exclusiveMinimum) { - return Object_assign( - boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), - { exclusiveMinimum }, - ) -} -number_.lessThan = function numberLessThan(exclusiveMaximum) { - return Object_assign( - boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), - { exclusiveMaximum }, - ) -} -number_.between = function numberBetween( - min, - max, - minimum = Math_min(min, max), - maximum = Math_max(min, max), -) { - return Object_assign( - boundedNumber({ gte: minimum, lte: maximum }), - { minimum, maximum }, - ) -} - -Object_assign( - number_, - bindUserExtensions(number_, userExtensions), -) - -function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ -function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ -function boundedNumber(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedNumberSchema(u: unknown) { - return typeof u === 'number' && within(bounds)(u) - }, carry, number_) -} - -declare namespace number_ { - interface core extends number_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: number - tag: URI.number - get def(): this['_type'] - minimum?: number - maximum?: number - exclusiveMinimum?: number - exclusiveMaximum?: number - } - interface methods { - min(minimum: Min): number_.Min - max(maximum: Max): number_.Max - moreThan(moreThan: Min): ExclusiveMin - lessThan(lessThan: Max): ExclusiveMax - between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ exclusiveMaximum: number }] - ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> - : [Self] extends [{ maximum: number }] - ? number_.between<[min: X, max: Self['maximum']]> - : number_.min - ; - type Max - = [Self] extends [{ exclusiveMinimum: number }] - ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> - : [Self] extends [{ minimum: number }] - ? number_.between<[min: Self['minimum'], max: X]> - : number_.max - ; - type ExclusiveMin - = [Self] extends [{ exclusiveMaximum: number }] - ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> - : [Self] extends [{ maximum: number }] - ? number_.maxStrictMin<[min: X, Self['maximum']]> - : number_.moreThan - ; - type ExclusiveMax - = [Self] extends [{ exclusiveMinimum: number }] - ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> - : [Self] extends [{ minimum: number }] - ? number_.minStrictMax<[Self['minimum'], min: X]> - : number_.lessThan - ; - interface min extends number_ { minimum: Min } - interface max extends number_ { maximum: Max } - interface moreThan extends number_ { exclusiveMinimum: Min } - interface lessThan extends number_ { exclusiveMaximum: Max } - interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } - interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } - interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } - interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } -} diff --git a/packages/schema/src/__schemas__/object.ts b/packages/schema/src/__schemas__/object.ts deleted file mode 100644 index 4aa57c18..00000000 --- a/packages/schema/src/__schemas__/object.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { Force, SchemaOptions as Options, Unknown } from '@traversable/registry' -import { - applyOptions, - Array_isArray, - bindUserExtensions, - has, - _isPredicate, - Object_assign, - Object_keys, - record as record$, - object as object$, - isAnyObject, - symbol, - URI, -} from '@traversable/registry' - -import type { Entry, Optional, Required, Schema, SchemaLike } from '@traversable/schema-core/namespace' - -export { object_ as object } - -function object_< - S extends { [x: string]: Schema }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_< - S extends { [x: string]: SchemaLike }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_(schemas: { [x: string]: Schema }, options?: Options) { - return object_.def(schemas, options) -} - -interface object_ extends object_.core { - //<%= Types %> -} - -namespace object_ { - export let userDefinitions: Record = { - //<%= Definitions %> - } as object_ - export function def(xs: T, $?: Options, opt?: string[]): object_ - /* v8 ignore next 1 */ - export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const keys = Object_keys(xs) - const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) - const req = keys.filter((k) => !has(symbol.optional)(xs[k])) - const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) - function ObjectSchema(src: unknown) { return predicate(src) } - ObjectSchema.tag = URI.object - ObjectSchema.def = xs - ObjectSchema.opt = opt - ObjectSchema.req = req - Object_assign(ObjectSchema, userDefinitions) - return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) - } -} - -declare namespace object_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: object_.type - tag: URI.object - get def(): S - opt: Optional - req: Required - } - type type< - S, - Opt extends Optional = Optional, - Req extends Required = Required, - T = Force< - & { [K in Req]-?: S[K]['_type' & keyof S[K]] } - & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } - > - > = never | T -} diff --git a/packages/schema/src/__schemas__/of.ts b/packages/schema/src/__schemas__/of.ts deleted file mode 100644 index 8f6f9030..00000000 --- a/packages/schema/src/__schemas__/of.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -import type { - Entry, - Guard, - Guarded, - SchemaLike, -} from '@traversable/schema-core/namespace' - -export interface of extends of.core { - //<%= Types %> -} - -export function of(typeguard: S): Entry -export function of(typeguard: S): of -export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { - typeguard.def = typeguard - return Object_assign(typeguard, of.prototype) -} - -export namespace of { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(guard: T): of - /* v8 ignore next 6 */ - export function def(guard: T) { - function InlineSchema(src: unknown) { return guard(src) } - InlineSchema.tag = URI.inline - InlineSchema.def = guard - return InlineSchema - } -} - -export declare namespace of { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: Guarded - tag: URI.inline - get def(): S - } - type type> = never | T -} diff --git a/packages/schema/src/__schemas__/optional.ts b/packages/schema/src/__schemas__/optional.ts deleted file mode 100644 index ba7120bc..00000000 --- a/packages/schema/src/__schemas__/optional.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - bindUserExtensions, - has, - _isPredicate, - optional as optional$, - Object_assign, - symbol, - URI, - isUnknown as isAny, -} from '@traversable/registry' - -import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' - -export function optional(schema: S): optional -export function optional(schema: S): optional> -export function optional(schema: S): optional { return optional.def(schema) } - -export interface optional extends optional.core { - //<%= Types %> -} - -export namespace optional { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(x: T): optional - export function def(x: T) { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = _isPredicate(x) ? optional$(x) : isAny - function OptionalSchema(src: unknown) { return predicate(src) } - OptionalSchema.tag = URI.optional - OptionalSchema.def = x - OptionalSchema[symbol.optional] = 1 - Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) - return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) - } - export const is - : (u: unknown) => u is optional - /* v8 ignore next 1 */ - = has('tag', (u) => u === URI.optional) -} - -export declare namespace optional { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.optional - _type: undefined | S['_type' & keyof S] - def: S - [symbol.optional]: number - } - export type type = never | T -} diff --git a/packages/schema/src/__schemas__/record.ts b/packages/schema/src/__schemas__/record.ts deleted file mode 100644 index b5838df0..00000000 --- a/packages/schema/src/__schemas__/record.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - isAnyObject, - record as record$, - bindUserExtensions, - _isPredicate, - Object_assign, - URI, -} from '@traversable/registry' - -import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' - -export function record(schema: S): record -export function record(schema: S): record> -export function record(schema: Schema) { - return record.def(schema) -} - -export interface record extends record.core { - //<%= Types %> -} - -export namespace record { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(x: T): record - /* v8 ignore next 1 */ - export function def(x: unknown): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = _isPredicate(x) ? record$(x) : isAnyObject - function RecordSchema(src: unknown) { return predicate(src) } - RecordSchema.tag = URI.record - RecordSchema.def = x - Object_assign(RecordSchema, record.userDefinitions) - return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) - } -} - -export declare namespace record { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.record - get def(): S - _type: Record - } - export type type> = never | T -} diff --git a/packages/schema/src/__schemas__/string.ts b/packages/schema/src/__schemas__/string.ts deleted file mode 100644 index 4276ca17..00000000 --- a/packages/schema/src/__schemas__/string.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { Bounds, Integer, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_min, - Math_max, - Object_assign, - URI, - within, -} from '@traversable/registry' - -export { string_ as string } - -/** @internal */ -function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ -function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ -function boundedString(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedStringSchema(u: unknown) { - return string_(u) && within(bounds)(u.length) - }, carry, string_) -} - -interface string_ extends string_.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function StringSchema(src: unknown) { return typeof src === 'string' } -StringSchema.tag = URI.string -StringSchema.def = '' - -const string_ = Object_assign( - StringSchema, - userDefinitions, -) as string_ - -string_.min = function stringMinLength(minLength) { - return Object_assign( - boundedString({ gte: minLength }, carryover(this, 'minLength')), - { minLength }, - ) -} -string_.max = function stringMaxLength(maxLength) { - return Object_assign( - boundedString({ lte: maxLength }, carryover(this, 'maxLength')), - { maxLength }, - ) -} -string_.between = function stringBetween( - min, - max, - minLength = Math_min(min, max), - maxLength = Math_max(min, max)) { - return Object_assign( - boundedString({ gte: minLength, lte: maxLength }), - { minLength, maxLength }, - ) -} - -Object_assign( - string_, - bindUserExtensions(string_, userExtensions), -) - -declare namespace string_ { - interface core extends string_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: string - tag: URI.string - get def(): this['_type'] - } - interface methods { - minLength?: number - maxLength?: number - min>(minLength: Min): string_.Min - max>(maxLength: Max): string_.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ maxLength: number }] - ? string_.between<[min: Min, max: Self['maxLength']]> - : string_.min - ; - type Max - = [Self] extends [{ minLength: number }] - ? string_.between<[min: Self['minLength'], max: Max]> - : string_.max - ; - interface min extends string_ { minLength: Min } - interface max extends string_ { maxLength: Max } - interface between extends string_ { - minLength: Bounds[0] - maxLength: Bounds[1] - } -} diff --git a/packages/schema/src/__schemas__/symbol.ts b/packages/schema/src/__schemas__/symbol.ts deleted file mode 100644 index 6e192b3e..00000000 --- a/packages/schema/src/__schemas__/symbol.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { symbol_ as symbol } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface symbol_ extends symbol_.core { - //<%= Types %> -} - -function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } -SymbolSchema.tag = URI.symbol -SymbolSchema.def = Symbol() - -const symbol_ = Object_assign( - SymbolSchema, - userDefinitions, -) as symbol_ - -Object_assign(symbol_, userExtensions) - -declare namespace symbol_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.symbol - _type: symbol - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/tuple.ts b/packages/schema/src/__schemas__/tuple.ts deleted file mode 100644 index ed906497..00000000 --- a/packages/schema/src/__schemas__/tuple.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { - SchemaOptions as Options, - TypeError, - Unknown -} from '@traversable/registry' - -import { - Array_isArray, - bindUserExtensions, - getConfig, - has, - _isPredicate, - Object_assign, - parseArgs, - symbol, - tuple as tuple$, - URI, -} from '@traversable/registry' - -import type { - Entry, - FirstOptionalItem, - invalid, - Schema, - SchemaLike, - TupleType, - ValidateTuple -} from '@traversable/schema-core/namespace' - -import type { optional } from './optional.js' - -export { tuple } - -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> -function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { - return tuple.def(...parseArgs(getConfig().schema, args)) -} - -interface tuple extends tuple.core { - //<%= Types %> -} - -namespace tuple { - export let userDefinitions: Record = { - //<%= Definitions %> - } as tuple - export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple - /* v8 ignore next 1 */ - export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const opt = opt_ || xs.findIndex(has(symbol.optional)) - const options = { - ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) - } satisfies tuple.InternalOptions - const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) - function TupleSchema(src: unknown) { return predicate(src) } - TupleSchema.tag = URI.tuple - TupleSchema.def = xs - TupleSchema.opt = opt - Object_assign(TupleSchema, tuple.userDefinitions) - return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) - } -} - -declare namespace tuple { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.tuple - _type: TupleType - opt: FirstOptionalItem - def: S - } - type type> = never | T - type InternalOptions = { minLength?: number } - type validate = ValidateTuple> - - type from - = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T -} diff --git a/packages/schema/src/__schemas__/undefined.ts b/packages/schema/src/__schemas__/undefined.ts deleted file mode 100644 index 0115f168..00000000 --- a/packages/schema/src/__schemas__/undefined.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { undefined_ as undefined } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface undefined_ extends undefined_.core { - //<%= Types %> -} - -function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } -UndefinedSchema.tag = URI.undefined -UndefinedSchema.def = void 0 as undefined - -const undefined_ = Object_assign( - UndefinedSchema, - userDefinitions, -) as undefined_ - -Object_assign(undefined_, userExtensions) - -declare namespace undefined_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.undefined - _type: undefined - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/union.ts b/packages/schema/src/__schemas__/union.ts deleted file mode 100644 index 33698c38..00000000 --- a/packages/schema/src/__schemas__/union.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - _isPredicate, - bindUserExtensions, - isUnknown as isAny, - Object_assign, - union as union$, - URI, -} from '@traversable/registry' - -import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' - -export function union(...schemas: S): union -export function union }>(...schemas: S): union -export function union(...schemas: unknown[]) { - return union.def(schemas) -} - -export interface union extends union.core { - //<%= Types %> -} - -export namespace union { - export let userDefinitions: Record = { - //<%= Definitions %> - } as Partial> - export function def(xs: T): union - /* v8 ignore next 1 */ - export function def(xs: unknown[]) { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = xs.every(_isPredicate) ? union$(xs) : isAny - function UnionSchema(src: unknown): src is unknown { return predicate(src) } - UnionSchema.tag = URI.union - UnionSchema.def = xs - Object_assign(UnionSchema, union.userDefinitions) - return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) - } -} - -export declare namespace union { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.union - _type: union.type - get def(): S - } - type type = never | T -} diff --git a/packages/schema/src/__schemas__/unknown.ts b/packages/schema/src/__schemas__/unknown.ts deleted file mode 100644 index 0a190db3..00000000 --- a/packages/schema/src/__schemas__/unknown.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { unknown_ as unknown } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface unknown_ extends unknown_.core { - //<%= Types %> -} - -function UnknownSchema(src: unknown): src is unknown { return true } -UnknownSchema.tag = URI.unknown -UnknownSchema.def = void 0 as unknown - -const unknown_ = Object_assign( - UnknownSchema, - userDefinitions, -) as unknown_ - -Object_assign(unknown_, userExtensions) - -declare namespace unknown_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.unknown - _type: unknown - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/void.ts b/packages/schema/src/__schemas__/void.ts deleted file mode 100644 index d178213e..00000000 --- a/packages/schema/src/__schemas__/void.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { void_ as void, void_ } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface void_ extends void_.core { - //<%= Types %> -} - -function VoidSchema(src: unknown): src is void { return src === void 0 } -VoidSchema.tag = URI.void -VoidSchema.def = void 0 as void - -const void_ = Object_assign( - VoidSchema, - userDefinitions, -) as void_ - -Object_assign(void_, userExtensions) - -declare namespace void_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.void - _type: void - get def(): this['_type'] - } -} diff --git a/packages/schema/src/build.ts b/packages/schema/src/build.ts index 53141e8d..37d9dde3 100755 --- a/packages/schema/src/build.ts +++ b/packages/schema/src/build.ts @@ -1,7 +1,23 @@ #!/usr/bin/env pnpm dlx tsx import * as path from 'node:path' import * as fs from 'node:fs' +import type { pick, omit, Returns, IfReturns } from '@traversable/registry' import { fn } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +let PATH = { + sourcesDir: path.join(path.resolve(), 'node_modules', '@traversable'), + target: path.join(path.resolve(), 'src', 'schemas'), +} + +type Library = typeof Library[keyof typeof Library] +let Library = { + Core: 'schema-core', + Equals: 'derive-equals', + ToJsonSchema: 'schema-to-json-schema', + ToString: 'schema-to-string', + Validators: 'derive-validators', +} as const let SCHEMA_WHITELIST = [ 'of', @@ -29,157 +45,404 @@ let SCHEMA_WHITELIST = [ 'object', ] -let FILE_BLACKLIST = [ - 'README.md', -] as const satisfies any[] - let LIB_BLACKLIST = [ 'registry', ] satisfies any[] -let LIB_WHITELIST = [ - 'derive-equals', - // 'schema-to-json-schema', -] satisfies any[] - -let LIB_NAME_TO_FILENAME = { - 'derive-equals': 'equals.ts', - 'schema-to-json-schema': 'toJsonSchema.ts', -} - -let PATH = { - sourcesDir: path.join(path.resolve(), 'node_modules', '@traversable'), - target: path.join(path.resolve(), 'src', 'schemas'), -} +let LIB_NAME_TO_TARGET_FILENAME = { + [Library.Core]: 'core', + [Library.Equals]: 'equals', + [Library.ToJsonSchema]: 'toJsonSchema', + [Library.Validators]: 'validate', + [Library.ToString]: 'toString', +} as const satisfies Record let RelativeImport = { namespace: { - /** + /** * @example - * import { stuff } from '../namespace.js' + * // from: + * import type { Guarded, Schema, SchemaLike } from '../../_namespace.js' + * // to: + * import type { Guarded, Schema, SchemaLike } from '../namespace.js' */ - from: /(\.\.\/)namespace.js/g, - to: '@traversable/schema-core/namespace' + from: /'(\.\.\/)namespace.js'/g, + to: '\'../../_namespace.js\'', }, - local: { - /** - * @example - * import type { of } from './of.js' - */ - from: /\.\/([^]*?)\.js/g, - /** - * @example - * import type { of } from '../of/core.js' - */ - to: (mod: string) => `.${mod.slice(0, -'.js'.length)}/core.js`, + local: (libShortName: string) => ({ + + from: /'\.\/(.+).js'/g, + to: (_: string, p1: string, p2: string) => `'../${p1}/${libShortName}'`, + + }), + parent: { + from: /'@traversable\/schema-core'/g, + to: '\'../../_exports.js\'', }, } let isKeyOf = (k: keyof any, t: T): k is keyof T => !!t && (typeof t === 'object' || typeof t === 'function') && k in t -function buildCoreSchemas() { - if (!fs.existsSync(PATH.target)) { - fs.mkdirSync(PATH.target) + +type GetTargetFileName = (libName: string, schemaName: string) => `${string}.ts` +type PostProcessor = (sourceFileContent: string, libConfig: LibOptions & { targetFileName: string }) => string + +type LibOptions = t.typeof +let LibOptions = t.object({ + relativePath: t.string, + getTargetFileName: (x): x is GetTargetFileName => typeof x === 'function', + postprocessor: (x): x is PostProcessor => typeof x === 'function', + // TODO: actually exclude files + excludeFiles: t.array(t.string), + includeFiles: t.optional(t.array(t.string)), +}) + +type BuildOptions = t.typeof +let BuildOptions = t.object({ + dryRun: t.optional(t.boolean), + getSourceDir: t.optional((x): x is (() => string) => typeof x === 'function'), + getTargetDir: t.optional((x): x is (() => string) => typeof x === 'function'), +}) + +type LibsOptions = never | { libs: Record> } +type LibsConfig = never | { libs: Record } +type ParseOptions = never | { [K in keyof T as K extends `get${infer P}` ? Uncapitalize

: K]-?: IfReturns } +type BuildConfig = ParseOptions + +type Options = + & BuildOptions + & LibsOptions + +type Config = + & BuildConfig + & LibsConfig + +let defaultGetTargetFileName = ( + (libName, _schemaName) => isKeyOf(libName, LIB_NAME_TO_TARGET_FILENAME) + ? `${LIB_NAME_TO_TARGET_FILENAME[libName]}.ts` as const + : `${libName}.ts` +) satisfies LibOptions['getTargetFileName'] + +let defaultPostProcessor = ( + (sourceFileContent, { targetFileName }) => { + let replaceLocalImports = RelativeImport.local(targetFileName) + return fn.pipe( + sourceFileContent, + (_) => _.replaceAll( + RelativeImport.namespace.from, + RelativeImport.namespace.to, + ), + (_) => _.replaceAll( + replaceLocalImports.from, + replaceLocalImports.to, + ), + (_) => _.replaceAll( + RelativeImport.parent.from, + RelativeImport.parent.to, + ), + ) } - return fs.readdirSync(PATH.sourcesDir, { withFileTypes: true }) - .filter(({ name }) => name === 'schema-core') +) satisfies PostProcessor + +let defaultLibOptions = { + relativePath: 'src/schemas', + excludeFiles: [], + getTargetFileName: defaultGetTargetFileName, + postprocessor: defaultPostProcessor, +} satisfies LibOptions + +let defaultLibs = { + [Library.Core]: defaultLibOptions, + [Library.Equals]: defaultLibOptions, + [Library.ToJsonSchema]: defaultLibOptions, + [Library.ToString]: defaultLibOptions, + [Library.Validators]: defaultLibOptions, +} satisfies Record + +let defaultOptions = { + dryRun: false, + getSourceDir: () => path.join(process.cwd(), 'node_modules', '@traversable'), + getTargetDir: () => path.join(process.cwd(), 'src', 'schemas'), + libs: defaultLibs, +} satisfies Required & LibsOptions + +function parseLibOptions({ + excludeFiles = defaultLibOptions.excludeFiles, + relativePath = defaultLibOptions.relativePath, + getTargetFileName = defaultLibOptions.getTargetFileName, + postprocessor = defaultLibOptions.postprocessor, + includeFiles, +}: Partial): LibOptions { + return { + excludeFiles, + relativePath, + getTargetFileName, + postprocessor, + ...includeFiles && { includeFiles } + } +} + +function parseOptions(options: Options): Config +function parseOptions({ + getSourceDir = defaultOptions.getSourceDir, + getTargetDir = defaultOptions.getTargetDir, + dryRun = defaultOptions.dryRun, + libs, +}: Options = defaultOptions): Config { + return { + dryRun, + sourceDir: getSourceDir(), + targetDir: getTargetDir(), + libs: fn.map(libs, parseLibOptions), + } +} + +let tap + : (msg?: string, stringify?: (x: unknown) => string) => (x: T,) => T + = (msg, stringify) => (x) => ( + console.log('\n' + (msg ? `${msg}:\n` : '') + (stringify ? stringify(x) : x) + '\r'), + x + ) + +function ensureDirExists(cache: Set, $: Config) { + return (schemaFile: fs.Dirent) => { + let targetDirPath = path.join( + PATH.target, + schemaFile.name.slice(0, -'.ts'.length), + ) + if (!cache.has(targetDirPath) && !$.dryRun) { + cache.add(targetDirPath) + if (!fs.existsSync(targetDirPath)) { + fs.mkdirSync(targetDirPath) + } + } + return schemaFile + } +} + +function buildCoreSchemas(options: Options) { + let $ = parseOptions(options) + let cache = new Set() + + return fs.readdirSync( + path.join($.sourceDir), { withFileTypes: true }) + .filter(({ name }) => Object.keys($.libs).includes(name)) .map( - ({ name, parentPath }) => fn.pipe( - path.join( - parentPath, - name, - 'src', - 'schemas' - ), - (absolutePath) => fs.readdirSync(absolutePath, { withFileTypes: true }), - (schemaPaths) => { - schemaPaths.forEach((schemaPath) => { - let dirName = schemaPath.name.endsWith('.ts') - ? schemaPath.name.slice(0, -'.ts'.length) - : schemaPath.name - let targetDirPath = path.join( - PATH.target, - dirName, - ) - if (!fs.existsSync(targetDirPath)) { - fs.mkdirSync(targetDirPath) - } - }) - return schemaPaths - }, - (schemaPaths) => schemaPaths.map(({ name, parentPath }) => [ + (sourceDir) => { + let LIB_NAME = sourceDir.name + let LIB = $.libs[LIB_NAME] + return fn.pipe( path.join( - PATH.target, - name.slice(0, -'.ts'.length), - 'core.ts', + sourceDir.parentPath, + LIB_NAME, + $.libs[LIB_NAME].relativePath, + ), + (schemasDir) => fs.readdirSync(schemasDir, { withFileTypes: true }), + fn.map( + (schemaFile) => { + let sourceFilePath = path.join(schemaFile.parentPath, schemaFile.name) + let targetFileName = LIB.getTargetFileName(LIB_NAME, schemaFile.name) + let targetDirName = schemaFile.name.endsWith('.ts') ? schemaFile.name.slice(0, -'.ts'.length) : schemaFile.name + let targetFilePath = path.join( + $.targetDir, + targetDirName, + targetFileName + ) + + return fn.pipe( + schemaFile, + ensureDirExists(cache, $), + (schemaFile) => { + return [ + targetFilePath, + fs.readFileSync(sourceFilePath).toString('utf8'), + ] satisfies [any, any] + }, + ([targetFilePath, sourceFileContent]) => [ + targetFilePath, + LIB.postprocessor(sourceFileContent, { ...LIB, targetFileName }) + ] satisfies [any, any], + ([targetFilePath, content]) => ( + void ((!$.dryRun && fs.writeFileSync(targetFilePath, content))), + [targetFilePath, content] + ) + ) + } ), - fs.readFileSync( - path.join( - parentPath, - name, - ) - ).toString('utf8') - ] satisfies [any, any]), - fn.map( - fn.flow( - ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], - ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local.from, RelativeImport.local.to)] satisfies [any, any], - ([filePath, content]) => fs.writeFileSync(filePath, content) - ) + // fn.map( + // fn.flow( + // ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], + // ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local(LIB_NAME).from, RelativeImport.local(LIB_NAME).to)] satisfies [any, any], + // ([filePath, content]) => (void (!$.dryRun && fs.writeFileSync(filePath, content)), [filePath, content]), + // ) + // ) + // tap('got em?') ) - ) - ) + }) } -function buildSchemaExtensions() { - return fs.readdirSync(PATH.sourcesDir, { withFileTypes: true }) - .filter(({ name }) => !LIB_BLACKLIST.includes(name) && LIB_WHITELIST.includes(name)) - .map( - ({ name, parentPath }) => fn.pipe( - path.join( - parentPath, - name, - 'src', - 'schemas', - ), - (absolutePath) => fs.readdirSync(absolutePath, { withFileTypes: true }), - (schemaPaths) => schemaPaths.map( - ({ name: schemaName, parentPath }) => [ - path.join( - PATH.target, - schemaName.slice(0, -'.ts'.length), - isKeyOf(name, LIB_NAME_TO_FILENAME) ? LIB_NAME_TO_FILENAME[name] : 'BORKED', - ), - fs.readFileSync( - path.join( - parentPath, - schemaName, - ) - ).toString('utf8') - ] satisfies [any, any] - ), - fn.map( - fn.flow( - ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], - ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local.from, RelativeImport.local.to)] satisfies [any, any], - ([filePath, content]) => fs.writeFileSync(filePath, content)), - ), - ) - ) +// (schemaFiles) => { +// schemaFiles.forEach((schemaFile) => { +// let targetDirPath = path.join( +// PATH.target, +// schemaFile.name.slice(0, -'.ts'.length), +// ) +// if (!cache.has(targetDirPath)) { +// cache.add(targetDirPath) +// if (!$.dryRun) { +// if (!fs.existsSync(targetDirPath)) { +// fs.mkdirSync(targetDirPath) +// } +// } +// else { +// console.group('\n\r[DRY_RUN]\r') +// console.log('[DEBUG]: !cache.has("' + targetDirPath + '")') +// if (!fs.existsSync(targetDirPath)) +// console.log('[DEBUG]: fs.mkdirSync("' + targetDirPath + '")') +// else +// console.log('[DEBUG]: fs.existsSync("' + targetDirPath + '")') +// console.groupEnd() +// } +// } else { +// if ($.dryRun) { +// console.group('\n\r[DRY_RUN]\r') +// console.log('[DEBUG]: cache.has("' + targetDirPath + '")') +// console.groupEnd() +// } + +// } +// }) +// return schemaFiles +// }, + +// (schemaFiles) => schemaFiles.map(({ name: schemaFileName, parentPath }) => { +// let schemaName = schemaFileName.slice(0, -'.ts'.length) + +// if (!sKeyOf(sourceDir.name, LIB_NAME_TO_FILENAME)) throw Error('dirName ' + dirName + ' is not a key of LIB_NAME_TO_FILENAME') +// else { +// return [ +// path.join( +// PATH.target, +// dirName.slice(0, -'.ts'.length), +// isKeyOf(dirName, LIB_NAME_TO_FILENAME) ? LIB_NAME_TO_FILENAME[dirName] + '.ts' : 'BORKED' +// ), +// fs.readFileSync( +// path.join( +// parentPath, +// dirName, +// ) +// ).toString('utf8') +// ] satisfies [any, any] +// } + +// }, +// ) + +// else { +// console.group('\n\r[DRY_RUN]\r') +// console.log('[DEBUG]: !cache.has("' + targetDirPath + '")') +// if (!fs.existsSync(targetDirPath)) +// console.log('[DEBUG]: fs.mkdirSync("' + targetDirPath + '")') +// else +// console.log('[DEBUG]: fs.existsSync("' + targetDirPath + '")') +// console.groupEnd() +// } +// } else { +// if ($.dryRun) { +// console.group('\n\r[DRY_RUN]\r') +// console.log('[DEBUG]: cache.has("' + targetDirPath + '")') +// console.groupEnd() +// } + + +// (schemaPaths) => schemaPaths.map(({ name: schemaFileName, parentPath }) => { +// let schemaName = schemaFileName.slice(0, -'.ts'.length) +// if (!isKeyOf(dirName, LIB_NAME_TO_FILENAME)) throw Error('dirName ' + dirName + ' is not a key of LIB_NAME_TO_FILENAME') +// else { +// return [ +// path.join( +// PATH.target, +// dirName.slice(0, -'.ts'.length), +// isKeyOf(dirName, LIB_NAME_TO_FILENAME) ? LIB_NAME_TO_FILENAME[dirName] + '.ts' : 'BORKED' +// ), +// fs.readFileSync( +// path.join( +// parentPath, +// dirName, +// ) +// ).toString('utf8') +// ] satisfies [any, any] +// } +// }, +// fn.map( +// fn.flow( +// ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], +// ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local.from, RelativeImport.local.to)] satisfies [any, any], +// ([filePath, content]) => !$.dryRun ? fs.writeFileSync(filePath, content) : void 0 +// ) +// ) +// ) +// ) +// ) + +// function buildSchemaExtensions() { +// return fs.readdirSync(PATH.sourcesDir, { withFileTypes: true }) +// .filter(({ name }) => !LIB_BLACKLIST.includes(name) && LIB_WHITELIST.includes(name)) +// .map( +// ({ name, parentPath }) => fn.pipe( +// path.join( +// parentPath, +// name, +// 'src', +// 'schemas', +// ), +// (absolutePath) => fs.readdirSync(absolutePath, { withFileTypes: true }), +// (schemaPaths) => schemaPaths.map(({ name: schemaName, parentPath }) => [ +// path.join( +// PATH.target, +// schemaName.slice(0, -'.ts'.length), +// isKeyOf(name, LIB_NAME_TO_FILENAME) ? LIB_NAME_TO_FILENAME[name] + '.ts' : 'BORKED', +// ), +// fs.readFileSync( +// path.join( +// parentPath, +// schemaName, +// ) +// ).toString('utf8') +// ] satisfies [any, any]), +// fn.map( +// fn.flow( +// ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], +// ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local.from, RelativeImport.local.to)] satisfies [any, any], +// ([filePath, content]) => fs.writeFileSync(filePath, content)), +// ), +// ) +// ) +// } + +function ensureTargetExists() { + if (!fs.existsSync(PATH.target)) { + fs.mkdirSync(PATH.target) + } } -function buildSchemas() { - buildCoreSchemas() - buildSchemaExtensions() +function buildSchemas(options: Options) { + ensureTargetExists() + return buildCoreSchemas(options) + // buildSchemaExtensions() } -buildSchemas() +let out = buildSchemas(defaultOptions) + +// console.log('out', out) /** * ## TODO * * - [x] Pull the .ts files out of `@traversable/schema-core` * - [x] Pull the .ts files out of `@traversable/derive-equals` - * - [ ] Pull the .ts files out of `@traversable/schema-to-json-schema` + * - [x] Pull the .ts files out of `@traversable/schema-to-json-schema` + * - [ ] Pull the .ts files out of `@traversable/derive-validators` + * - [ ] Pull the .ts files out of `@traversable/schema-to-string` */ diff --git a/packages/schema/src/schemas/array/core.ts b/packages/schema/src/schemas/array/core.ts index 77624f60..3128d3cb 100644 --- a/packages/schema/src/schemas/array/core.ts +++ b/packages/schema/src/schemas/array/core.ts @@ -18,9 +18,9 @@ import { URI, } from '@traversable/registry' -import type { Guarded, Schema, SchemaLike } from '@traversable/schema-core/namespace' +import type { Guarded, Schema, SchemaLike } from '../../_namespace.js' -import type { of } from '../of/core.js' +import type { of } from '../of/core.ts' /** @internal */ function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array diff --git a/packages/schema/src/schemas/array/equals.ts b/packages/schema/src/schemas/array/equals.ts index aa06a7bf..c09f189a 100644 --- a/packages/schema/src/schemas/array/equals.ts +++ b/packages/schema/src/schemas/array/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { has, Array_isArray, Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' +import type { t } from '../../_exports.js' export type equals = never | Equal diff --git a/packages/schema/src/schemas/bigint/equals.ts b/packages/schema/src/schemas/bigint/equals.ts index 3f38a8a5..7c98bbf1 100644 --- a/packages/schema/src/schemas/bigint/equals.ts +++ b/packages/schema/src/schemas/bigint/equals.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: bigint, right: bigint): boolean { diff --git a/packages/schema/src/schemas/boolean/equals.ts b/packages/schema/src/schemas/boolean/equals.ts index c060588a..306bb12b 100644 --- a/packages/schema/src/schemas/boolean/equals.ts +++ b/packages/schema/src/schemas/boolean/equals.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: boolean, right: boolean): boolean { diff --git a/packages/schema/src/schemas/eq/equals.ts b/packages/schema/src/schemas/eq/equals.ts index 9df41231..8b6f5552 100644 --- a/packages/schema/src/schemas/eq/equals.ts +++ b/packages/schema/src/schemas/eq/equals.ts @@ -1,5 +1,5 @@ import type { Equal } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import { t } from '../../_exports.js' export type equals = never | Equal export function equals(eqSchema: t.eq): equals diff --git a/packages/schema/src/schemas/integer/equals.ts b/packages/schema/src/schemas/integer/equals.ts index a599f588..29fcd602 100644 --- a/packages/schema/src/schemas/integer/equals.ts +++ b/packages/schema/src/schemas/integer/equals.ts @@ -1,5 +1,5 @@ import type { Equal } from '@traversable/registry' -import { SameValueNumber } from "@traversable/registry" +import { SameValueNumber } from '@traversable/registry' export type equals = Equal export function equals(left: number, right: number): boolean { diff --git a/packages/schema/src/schemas/intersect/core.ts b/packages/schema/src/schemas/intersect/core.ts index 916aa265..47dd97da 100644 --- a/packages/schema/src/schemas/intersect/core.ts +++ b/packages/schema/src/schemas/intersect/core.ts @@ -8,7 +8,7 @@ import { URI, } from '@traversable/registry' -import type { Entry, IntersectType, Schema, SchemaLike } from '@traversable/schema-core/namespace' +import type { Entry, IntersectType, Schema, SchemaLike } from '../../_namespace.js' export function intersect(...schemas: S): intersect export function intersect }>(...schemas: S): intersect diff --git a/packages/schema/src/schemas/intersect/equals.ts b/packages/schema/src/schemas/intersect/equals.ts index ce880aa1..f1faa5b1 100644 --- a/packages/schema/src/schemas/intersect/equals.ts +++ b/packages/schema/src/schemas/intersect/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' +import type { t } from '../../_exports.js' export type equals = Equal export function equals(intersectSchema: t.intersect<[...S]>): equals diff --git a/packages/schema/src/schemas/never/equals.ts b/packages/schema/src/schemas/never/equals.ts index 7348de1a..3ed89421 100644 --- a/packages/schema/src/schemas/never/equals.ts +++ b/packages/schema/src/schemas/never/equals.ts @@ -1,4 +1,4 @@ -import type { Equal } from "@traversable/registry" +import type { Equal } from '@traversable/registry' export type equals = Equal export function equals(left: never, right: never): boolean { diff --git a/packages/schema/src/schemas/null/equals.ts b/packages/schema/src/schemas/null/equals.ts index fe92c107..12c2f636 100644 --- a/packages/schema/src/schemas/null/equals.ts +++ b/packages/schema/src/schemas/null/equals.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: null, right: null): boolean { diff --git a/packages/schema/src/schemas/number/equals.ts b/packages/schema/src/schemas/number/equals.ts index a599f588..29fcd602 100644 --- a/packages/schema/src/schemas/number/equals.ts +++ b/packages/schema/src/schemas/number/equals.ts @@ -1,5 +1,5 @@ import type { Equal } from '@traversable/registry' -import { SameValueNumber } from "@traversable/registry" +import { SameValueNumber } from '@traversable/registry' export type equals = Equal export function equals(left: number, right: number): boolean { diff --git a/packages/schema/src/schemas/object/core.ts b/packages/schema/src/schemas/object/core.ts index 4aa57c18..5b6116df 100644 --- a/packages/schema/src/schemas/object/core.ts +++ b/packages/schema/src/schemas/object/core.ts @@ -14,7 +14,7 @@ import { URI, } from '@traversable/registry' -import type { Entry, Optional, Required, Schema, SchemaLike } from '@traversable/schema-core/namespace' +import type { Entry, Optional, Required, Schema, SchemaLike } from '../../_namespace.js' export { object_ as object } diff --git a/packages/schema/src/schemas/object/equals.ts b/packages/schema/src/schemas/object/equals.ts index 3cd8134f..ecc79d7a 100644 --- a/packages/schema/src/schemas/object/equals.ts +++ b/packages/schema/src/schemas/object/equals.ts @@ -1,6 +1,6 @@ import type * as T from '@traversable/registry' import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' +import type { t } from '../../_exports.js' export type equals = never | T.Equal export function equals(objectSchema: t.object): equals> diff --git a/packages/schema/src/schemas/of/core.ts b/packages/schema/src/schemas/of/core.ts index 8f6f9030..48e7a300 100644 --- a/packages/schema/src/schemas/of/core.ts +++ b/packages/schema/src/schemas/of/core.ts @@ -6,7 +6,7 @@ import type { Guard, Guarded, SchemaLike, -} from '@traversable/schema-core/namespace' +} from '../../_namespace.js' export interface of extends of.core { //<%= Types %> diff --git a/packages/schema/src/schemas/optional/core.ts b/packages/schema/src/schemas/optional/core.ts index ba7120bc..77714964 100644 --- a/packages/schema/src/schemas/optional/core.ts +++ b/packages/schema/src/schemas/optional/core.ts @@ -10,7 +10,7 @@ import { isUnknown as isAny, } from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' +import type { Entry, Schema, SchemaLike } from '../../_namespace.js' export function optional(schema: S): optional export function optional(schema: S): optional> diff --git a/packages/schema/src/schemas/optional/equals.ts b/packages/schema/src/schemas/optional/equals.ts index 25944376..5ae67e4c 100644 --- a/packages/schema/src/schemas/optional/equals.ts +++ b/packages/schema/src/schemas/optional/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' +import type { t } from '../../_exports.js' export type equals = never | Equal export function equals(optionalSchema: t.optional): equals diff --git a/packages/schema/src/schemas/record/core.ts b/packages/schema/src/schemas/record/core.ts index b5838df0..55819b72 100644 --- a/packages/schema/src/schemas/record/core.ts +++ b/packages/schema/src/schemas/record/core.ts @@ -8,7 +8,7 @@ import { URI, } from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' +import type { Entry, Schema, SchemaLike } from '../../_namespace.js' export function record(schema: S): record export function record(schema: S): record> diff --git a/packages/schema/src/schemas/record/equals.ts b/packages/schema/src/schemas/record/equals.ts index 07addff1..ecbf81d7 100644 --- a/packages/schema/src/schemas/record/equals.ts +++ b/packages/schema/src/schemas/record/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Array_isArray, Object_is, Object_keys, Object_hasOwn } from '@traversable/registry' -import type { t } from '@traversable/schema-core' +import type { t } from '../../_exports.js' export type equals = never | Equal export function equals(recordSchema: t.record): equals diff --git a/packages/schema/src/schemas/symbol/equals.ts b/packages/schema/src/schemas/symbol/equals.ts index 24e82beb..f3bb7486 100644 --- a/packages/schema/src/schemas/symbol/equals.ts +++ b/packages/schema/src/schemas/symbol/equals.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: symbol, right: symbol): boolean { diff --git a/packages/schema/src/schemas/tuple/core.ts b/packages/schema/src/schemas/tuple/core.ts index 2b277799..ce6e91cb 100644 --- a/packages/schema/src/schemas/tuple/core.ts +++ b/packages/schema/src/schemas/tuple/core.ts @@ -25,9 +25,9 @@ import type { SchemaLike, TupleType, ValidateTuple -} from '@traversable/schema-core/namespace' +} from '../../_namespace.js' -import type { optional } from '../optional/core.js' +import type { optional } from '../optional/core.ts' export { tuple } diff --git a/packages/schema/src/schemas/tuple/equals.ts b/packages/schema/src/schemas/tuple/equals.ts index 4d9de15c..54ea9a02 100644 --- a/packages/schema/src/schemas/tuple/equals.ts +++ b/packages/schema/src/schemas/tuple/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import { t } from '../../_exports.js' export type equals = Equal diff --git a/packages/schema/src/schemas/undefined/equals.ts b/packages/schema/src/schemas/undefined/equals.ts index 2836cd51..75156d56 100644 --- a/packages/schema/src/schemas/undefined/equals.ts +++ b/packages/schema/src/schemas/undefined/equals.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: undefined, right: undefined): boolean { diff --git a/packages/schema/src/schemas/union/core.ts b/packages/schema/src/schemas/union/core.ts index 33698c38..5fee08b4 100644 --- a/packages/schema/src/schemas/union/core.ts +++ b/packages/schema/src/schemas/union/core.ts @@ -8,7 +8,7 @@ import { URI, } from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from '@traversable/schema-core/namespace' +import type { Entry, Schema, SchemaLike } from '../../_namespace.js' export function union(...schemas: S): union export function union }>(...schemas: S): union diff --git a/packages/schema/src/schemas/union/equals.ts b/packages/schema/src/schemas/union/equals.ts index c0275e69..59900d7f 100644 --- a/packages/schema/src/schemas/union/equals.ts +++ b/packages/schema/src/schemas/union/equals.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' +import type { t } from '../../_exports.js' export type equals = Equal export function equals(unionSchema: t.union<[...S]>): equals diff --git a/packages/schema/src/schemas/unknown/equals.ts b/packages/schema/src/schemas/unknown/equals.ts index 41b30fd5..ccd2a780 100644 --- a/packages/schema/src/schemas/unknown/equals.ts +++ b/packages/schema/src/schemas/unknown/equals.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: any, right: any): boolean { diff --git a/packages/schema/src/schemas/void/equals.ts b/packages/schema/src/schemas/void/equals.ts index 6f7b9779..d11d89e3 100644 --- a/packages/schema/src/schemas/void/equals.ts +++ b/packages/schema/src/schemas/void/equals.ts @@ -1,5 +1,5 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' export type equals = Equal export function equals(left: void, right: void): boolean { diff --git a/packages/schema/tsconfig.build.json b/packages/schema/tsconfig.build.json index f1e02803..b947e124 100644 --- a/packages/schema/tsconfig.build.json +++ b/packages/schema/tsconfig.build.json @@ -10,6 +10,7 @@ "references": [ { "path": "../derive-codec" }, { "path": "../derive-equals" }, + { "path": "../derive-validators" }, { "path": "../registry" }, { "path": "../schema-core" }, { "path": "../schema-generator" }, diff --git a/packages/schema/tsconfig.src.json b/packages/schema/tsconfig.src.json index bbca465c..d4bc09fc 100644 --- a/packages/schema/tsconfig.src.json +++ b/packages/schema/tsconfig.src.json @@ -9,6 +9,7 @@ "references": [ { "path": "../derive-codec" }, { "path": "../derive-equals" }, + { "path": "../derive-validators" }, { "path": "../registry" }, { "path": "../schema-core" }, { "path": "../schema-generator" }, diff --git a/packages/schema/tsconfig.test.json b/packages/schema/tsconfig.test.json index 4f443a87..85850c64 100644 --- a/packages/schema/tsconfig.test.json +++ b/packages/schema/tsconfig.test.json @@ -10,6 +10,7 @@ { "path": "tsconfig.src.json" }, { "path": "../derive-codec" }, { "path": "../derive-equals" }, + { "path": "../derive-validators" }, { "path": "../registry" }, { "path": "../schema-core" }, { "path": "../schema-generator" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfc05b66..fc8ea261 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -243,6 +243,9 @@ importers: '@traversable/derive-equals': specifier: workspace:^ version: link:../derive-equals/dist + '@traversable/derive-validators': + specifier: workspace:^ + version: link:../derive-validators/dist '@traversable/registry': specifier: workspace:^ version: link:../registry/dist From b99bbab4e69f1c69124470ba25d2f5224891cf23 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sun, 13 Apr 2025 13:14:06 -0500 Subject: [PATCH 32/45] feat(generator): fully generating w/o type errors --- packages/derive-equals/src/schemas/of.ts | 6 + packages/derive-validators/src/errors.ts | 1 + packages/derive-validators/src/schemas/of.ts | 14 + packages/schema-core/src/schemas/of.ts | 3 + packages/schema-generator/src/imports.ts | 13 +- .../src/schemas/of.ts} | 4 +- .../src/schemas/of.ts} | 0 packages/schema/src/_namespace.ts | 44 +- packages/schema/src/build.ts | 450 +++++++----------- packages/schema/src/extensions/any.ts | 21 + packages/schema/src/extensions/array.ts | 20 + packages/schema/src/extensions/bigint.ts | 21 + packages/schema/src/extensions/boolean.ts | 21 + packages/schema/src/extensions/eq.ts | 20 + packages/schema/src/extensions/equals.ts | 3 + packages/schema/src/extensions/integer.ts | 21 + packages/schema/src/extensions/intersect.ts | 20 + packages/schema/src/extensions/never.ts | 21 + packages/schema/src/extensions/null.ts | 21 + packages/schema/src/extensions/number.ts | 21 + packages/schema/src/extensions/object.ts | 20 + packages/schema/src/extensions/of.ts | 21 + packages/schema/src/extensions/optional.ts | 21 + packages/schema/src/extensions/record.ts | 20 + packages/schema/src/extensions/string.ts | 21 + packages/schema/src/extensions/symbol.ts | 21 + .../schema/src/extensions/toJsonSchema.ts | 3 + packages/schema/src/extensions/toString.ts | 3 + packages/schema/src/extensions/tuple.ts | 20 + packages/schema/src/extensions/undefined.ts | 21 + packages/schema/src/extensions/union.ts | 20 + packages/schema/src/extensions/unknown.ts | 21 + packages/schema/src/extensions/validate.ts | 3 + packages/schema/src/extensions/void.ts | 21 + packages/schema/src/schemas/any.ts | 80 ++++ packages/schema/src/schemas/any/core.ts | 36 -- packages/schema/src/schemas/any/equals.ts | 7 - .../schema/src/schemas/any/toJsonSchema.ts | 5 - packages/schema/src/schemas/any/toString.ts | 2 - packages/schema/src/schemas/any/validate.ts | 10 - packages/schema/src/schemas/array.ts | 250 ++++++++++ packages/schema/src/schemas/array/core.ts | 128 ----- packages/schema/src/schemas/array/equals.ts | 24 - .../schema/src/schemas/array/toJsonSchema.ts | 36 -- packages/schema/src/schemas/array/toString.ts | 22 - packages/schema/src/schemas/array/validate.ts | 27 -- .../src/schemas/{bigint/core.ts => bigint.ts} | 63 ++- packages/schema/src/schemas/bigint/equals.ts | 7 - .../schema/src/schemas/bigint/toJsonSchema.ts | 7 - .../schema/src/schemas/bigint/toString.ts | 2 - .../schema/src/schemas/bigint/validate.ts | 13 - packages/schema/src/schemas/boolean.ts | 83 ++++ packages/schema/src/schemas/boolean/core.ts | 37 -- packages/schema/src/schemas/boolean/equals.ts | 7 - .../src/schemas/boolean/toJsonSchema.ts | 5 - .../schema/src/schemas/boolean/toString.ts | 2 - .../schema/src/schemas/boolean/validate.ts | 12 - packages/schema/src/schemas/eq.ts | 119 +++++ packages/schema/src/schemas/eq/core.ts | 41 -- packages/schema/src/schemas/eq/equals.ts | 8 - .../schema/src/schemas/eq/toJsonSchema.ts | 8 - packages/schema/src/schemas/eq/toString.ts | 17 - packages/schema/src/schemas/eq/validate.ts | 17 - .../schemas/{integer/core.ts => integer.ts} | 83 +++- packages/schema/src/schemas/integer/equals.ts | 7 - .../src/schemas/integer/toJsonSchema.ts | 23 - .../schema/src/schemas/integer/toString.ts | 2 - .../schema/src/schemas/integer/validate.ts | 13 - packages/schema/src/schemas/intersect.ts | 147 ++++++ packages/schema/src/schemas/intersect/core.ts | 50 -- .../schema/src/schemas/intersect/equals.ts | 16 - .../src/schemas/intersect/toJsonSchema.ts | 20 - .../schema/src/schemas/intersect/toString.ts | 18 - .../schema/src/schemas/intersect/validate.ts | 21 - packages/schema/src/schemas/never.ts | 80 ++++ packages/schema/src/schemas/never/core.ts | 37 -- packages/schema/src/schemas/never/equals.ts | 6 - .../schema/src/schemas/never/toJsonSchema.ts | 5 - packages/schema/src/schemas/never/toString.ts | 2 - packages/schema/src/schemas/never/validate.ts | 10 - packages/schema/src/schemas/null.ts | 86 ++++ packages/schema/src/schemas/null/core.ts | 39 -- packages/schema/src/schemas/null/equals.ts | 7 - .../schema/src/schemas/null/toJsonSchema.ts | 5 - packages/schema/src/schemas/null/toString.ts | 2 - packages/schema/src/schemas/null/validate.ts | 13 - .../src/schemas/{number/core.ts => number.ts} | 82 +++- packages/schema/src/schemas/number/equals.ts | 7 - .../schema/src/schemas/number/toJsonSchema.ts | 23 - .../schema/src/schemas/number/toString.ts | 2 - .../schema/src/schemas/number/validate.ts | 13 - packages/schema/src/schemas/object.ts | 305 ++++++++++++ packages/schema/src/schemas/object/core.ts | 79 --- packages/schema/src/schemas/object/equals.ts | 29 -- .../schema/src/schemas/object/toJsonSchema.ts | 27 -- .../schema/src/schemas/object/toString.ts | 41 -- .../schema/src/schemas/object/validate.ts | 110 ----- packages/schema/src/schemas/of.ts | 94 ++++ packages/schema/src/schemas/of/core.ts | 44 -- packages/schema/src/schemas/optional.ts | 135 ++++++ packages/schema/src/schemas/optional/core.ts | 55 --- .../schema/src/schemas/optional/equals.ts | 13 - .../src/schemas/optional/toJsonSchema.ts | 19 - .../schema/src/schemas/optional/toString.ts | 15 - .../schema/src/schemas/optional/validate.ts | 17 - packages/schema/src/schemas/record.ts | 160 +++++++ packages/schema/src/schemas/record/core.ts | 50 -- packages/schema/src/schemas/record/equals.ts | 32 -- .../schema/src/schemas/record/toJsonSchema.ts | 21 - .../schema/src/schemas/record/toString.ts | 17 - .../schema/src/schemas/record/validate.ts | 24 - .../src/schemas/{string/core.ts => string.ts} | 80 +++- packages/schema/src/schemas/string/equals.ts | 6 - .../schema/src/schemas/string/toJsonSchema.ts | 22 - .../schema/src/schemas/string/toString.ts | 2 - .../schema/src/schemas/string/validate.ts | 13 - packages/schema/src/schemas/symbol.ts | 83 ++++ packages/schema/src/schemas/symbol/core.ts | 36 -- packages/schema/src/schemas/symbol/equals.ts | 7 - .../schema/src/schemas/symbol/toJsonSchema.ts | 5 - .../schema/src/schemas/symbol/toString.ts | 2 - .../schema/src/schemas/symbol/validate.ts | 13 - packages/schema/src/schemas/tuple.ts | 223 +++++++++ packages/schema/src/schemas/tuple/core.ts | 86 ---- packages/schema/src/schemas/tuple/equals.ts | 27 -- .../schema/src/schemas/tuple/toJsonSchema.ts | 37 -- packages/schema/src/schemas/tuple/toString.ts | 26 - packages/schema/src/schemas/tuple/validate.ts | 34 -- packages/schema/src/schemas/undefined.ts | 83 ++++ packages/schema/src/schemas/undefined/core.ts | 36 -- .../schema/src/schemas/undefined/equals.ts | 7 - .../src/schemas/undefined/toJsonSchema.ts | 5 - .../schema/src/schemas/undefined/toString.ts | 2 - .../schema/src/schemas/undefined/validate.ts | 13 - packages/schema/src/schemas/union.ts | 144 ++++++ packages/schema/src/schemas/union/core.ts | 50 -- packages/schema/src/schemas/union/equals.ts | 16 - .../schema/src/schemas/union/toJsonSchema.ts | 17 - packages/schema/src/schemas/union/toString.ts | 18 - packages/schema/src/schemas/union/validate.ts | 26 - packages/schema/src/schemas/unknown.ts | 80 ++++ packages/schema/src/schemas/unknown/core.ts | 36 -- packages/schema/src/schemas/unknown/equals.ts | 7 - .../src/schemas/unknown/toJsonSchema.ts | 5 - .../schema/src/schemas/unknown/validate.ts | 10 - packages/schema/src/schemas/void.ts | 85 ++++ packages/schema/src/schemas/void/core.ts | 36 -- packages/schema/src/schemas/void/equals.ts | 7 - packages/schema/src/schemas/void/toString.ts | 2 - packages/schema/src/schemas/void/validate.ts | 13 - 150 files changed, 3199 insertions(+), 2363 deletions(-) create mode 100644 packages/derive-equals/src/schemas/of.ts create mode 100644 packages/derive-validators/src/schemas/of.ts rename packages/{schema/src/schemas/void/toJsonSchema.ts => schema-to-json-schema/src/schemas/of.ts} (62%) rename packages/{schema/src/schemas/unknown/toString.ts => schema-to-string/src/schemas/of.ts} (100%) create mode 100644 packages/schema/src/extensions/any.ts create mode 100644 packages/schema/src/extensions/array.ts create mode 100644 packages/schema/src/extensions/bigint.ts create mode 100644 packages/schema/src/extensions/boolean.ts create mode 100644 packages/schema/src/extensions/eq.ts create mode 100644 packages/schema/src/extensions/equals.ts create mode 100644 packages/schema/src/extensions/integer.ts create mode 100644 packages/schema/src/extensions/intersect.ts create mode 100644 packages/schema/src/extensions/never.ts create mode 100644 packages/schema/src/extensions/null.ts create mode 100644 packages/schema/src/extensions/number.ts create mode 100644 packages/schema/src/extensions/object.ts create mode 100644 packages/schema/src/extensions/of.ts create mode 100644 packages/schema/src/extensions/optional.ts create mode 100644 packages/schema/src/extensions/record.ts create mode 100644 packages/schema/src/extensions/string.ts create mode 100644 packages/schema/src/extensions/symbol.ts create mode 100644 packages/schema/src/extensions/toJsonSchema.ts create mode 100644 packages/schema/src/extensions/toString.ts create mode 100644 packages/schema/src/extensions/tuple.ts create mode 100644 packages/schema/src/extensions/undefined.ts create mode 100644 packages/schema/src/extensions/union.ts create mode 100644 packages/schema/src/extensions/unknown.ts create mode 100644 packages/schema/src/extensions/validate.ts create mode 100644 packages/schema/src/extensions/void.ts create mode 100644 packages/schema/src/schemas/any.ts delete mode 100644 packages/schema/src/schemas/any/core.ts delete mode 100644 packages/schema/src/schemas/any/equals.ts delete mode 100644 packages/schema/src/schemas/any/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/any/toString.ts delete mode 100644 packages/schema/src/schemas/any/validate.ts create mode 100644 packages/schema/src/schemas/array.ts delete mode 100644 packages/schema/src/schemas/array/core.ts delete mode 100644 packages/schema/src/schemas/array/equals.ts delete mode 100644 packages/schema/src/schemas/array/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/array/toString.ts delete mode 100644 packages/schema/src/schemas/array/validate.ts rename packages/schema/src/schemas/{bigint/core.ts => bigint.ts} (62%) delete mode 100644 packages/schema/src/schemas/bigint/equals.ts delete mode 100644 packages/schema/src/schemas/bigint/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/bigint/toString.ts delete mode 100644 packages/schema/src/schemas/bigint/validate.ts create mode 100644 packages/schema/src/schemas/boolean.ts delete mode 100644 packages/schema/src/schemas/boolean/core.ts delete mode 100644 packages/schema/src/schemas/boolean/equals.ts delete mode 100644 packages/schema/src/schemas/boolean/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/boolean/toString.ts delete mode 100644 packages/schema/src/schemas/boolean/validate.ts create mode 100644 packages/schema/src/schemas/eq.ts delete mode 100644 packages/schema/src/schemas/eq/core.ts delete mode 100644 packages/schema/src/schemas/eq/equals.ts delete mode 100644 packages/schema/src/schemas/eq/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/eq/toString.ts delete mode 100644 packages/schema/src/schemas/eq/validate.ts rename packages/schema/src/schemas/{integer/core.ts => integer.ts} (52%) delete mode 100644 packages/schema/src/schemas/integer/equals.ts delete mode 100644 packages/schema/src/schemas/integer/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/integer/toString.ts delete mode 100644 packages/schema/src/schemas/integer/validate.ts create mode 100644 packages/schema/src/schemas/intersect.ts delete mode 100644 packages/schema/src/schemas/intersect/core.ts delete mode 100644 packages/schema/src/schemas/intersect/equals.ts delete mode 100644 packages/schema/src/schemas/intersect/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/intersect/toString.ts delete mode 100644 packages/schema/src/schemas/intersect/validate.ts create mode 100644 packages/schema/src/schemas/never.ts delete mode 100644 packages/schema/src/schemas/never/core.ts delete mode 100644 packages/schema/src/schemas/never/equals.ts delete mode 100644 packages/schema/src/schemas/never/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/never/toString.ts delete mode 100644 packages/schema/src/schemas/never/validate.ts create mode 100644 packages/schema/src/schemas/null.ts delete mode 100644 packages/schema/src/schemas/null/core.ts delete mode 100644 packages/schema/src/schemas/null/equals.ts delete mode 100644 packages/schema/src/schemas/null/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/null/toString.ts delete mode 100644 packages/schema/src/schemas/null/validate.ts rename packages/schema/src/schemas/{number/core.ts => number.ts} (65%) delete mode 100644 packages/schema/src/schemas/number/equals.ts delete mode 100644 packages/schema/src/schemas/number/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/number/toString.ts delete mode 100644 packages/schema/src/schemas/number/validate.ts create mode 100644 packages/schema/src/schemas/object.ts delete mode 100644 packages/schema/src/schemas/object/core.ts delete mode 100644 packages/schema/src/schemas/object/equals.ts delete mode 100644 packages/schema/src/schemas/object/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/object/toString.ts delete mode 100644 packages/schema/src/schemas/object/validate.ts create mode 100644 packages/schema/src/schemas/of.ts delete mode 100644 packages/schema/src/schemas/of/core.ts create mode 100644 packages/schema/src/schemas/optional.ts delete mode 100644 packages/schema/src/schemas/optional/core.ts delete mode 100644 packages/schema/src/schemas/optional/equals.ts delete mode 100644 packages/schema/src/schemas/optional/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/optional/toString.ts delete mode 100644 packages/schema/src/schemas/optional/validate.ts create mode 100644 packages/schema/src/schemas/record.ts delete mode 100644 packages/schema/src/schemas/record/core.ts delete mode 100644 packages/schema/src/schemas/record/equals.ts delete mode 100644 packages/schema/src/schemas/record/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/record/toString.ts delete mode 100644 packages/schema/src/schemas/record/validate.ts rename packages/schema/src/schemas/{string/core.ts => string.ts} (54%) delete mode 100644 packages/schema/src/schemas/string/equals.ts delete mode 100644 packages/schema/src/schemas/string/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/string/toString.ts delete mode 100644 packages/schema/src/schemas/string/validate.ts create mode 100644 packages/schema/src/schemas/symbol.ts delete mode 100644 packages/schema/src/schemas/symbol/core.ts delete mode 100644 packages/schema/src/schemas/symbol/equals.ts delete mode 100644 packages/schema/src/schemas/symbol/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/symbol/toString.ts delete mode 100644 packages/schema/src/schemas/symbol/validate.ts create mode 100644 packages/schema/src/schemas/tuple.ts delete mode 100644 packages/schema/src/schemas/tuple/core.ts delete mode 100644 packages/schema/src/schemas/tuple/equals.ts delete mode 100644 packages/schema/src/schemas/tuple/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/tuple/toString.ts delete mode 100644 packages/schema/src/schemas/tuple/validate.ts create mode 100644 packages/schema/src/schemas/undefined.ts delete mode 100644 packages/schema/src/schemas/undefined/core.ts delete mode 100644 packages/schema/src/schemas/undefined/equals.ts delete mode 100644 packages/schema/src/schemas/undefined/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/undefined/toString.ts delete mode 100644 packages/schema/src/schemas/undefined/validate.ts create mode 100644 packages/schema/src/schemas/union.ts delete mode 100644 packages/schema/src/schemas/union/core.ts delete mode 100644 packages/schema/src/schemas/union/equals.ts delete mode 100644 packages/schema/src/schemas/union/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/union/toString.ts delete mode 100644 packages/schema/src/schemas/union/validate.ts create mode 100644 packages/schema/src/schemas/unknown.ts delete mode 100644 packages/schema/src/schemas/unknown/core.ts delete mode 100644 packages/schema/src/schemas/unknown/equals.ts delete mode 100644 packages/schema/src/schemas/unknown/toJsonSchema.ts delete mode 100644 packages/schema/src/schemas/unknown/validate.ts create mode 100644 packages/schema/src/schemas/void.ts delete mode 100644 packages/schema/src/schemas/void/core.ts delete mode 100644 packages/schema/src/schemas/void/equals.ts delete mode 100644 packages/schema/src/schemas/void/toString.ts delete mode 100644 packages/schema/src/schemas/void/validate.ts diff --git a/packages/derive-equals/src/schemas/of.ts b/packages/derive-equals/src/schemas/of.ts new file mode 100644 index 00000000..38809729 --- /dev/null +++ b/packages/derive-equals/src/schemas/of.ts @@ -0,0 +1,6 @@ +import { Equal } from '@traversable/registry' + +export type equals = Equal +export function equals(left: T, right: T): boolean { + return Equal.lax(left, right) +} diff --git a/packages/derive-validators/src/errors.ts b/packages/derive-validators/src/errors.ts index c669a6ce..23255d41 100644 --- a/packages/derive-validators/src/errors.ts +++ b/packages/derive-validators/src/errors.ts @@ -92,6 +92,7 @@ export const NULLARY = { array: (got, path) => error(ErrorType.TypeMismatch, path, got, 'Expected array'), record: (got, path) => error(ErrorType.TypeMismatch, path, got, 'Expected object'), optional: (got, path) => error(ErrorType.TypeMismatch, path, got, 'Expected optional'), + inline: (got, path) => error(ErrorType.TypeMismatch, path, got, 'Expected input to satisfy inline predicate', 'value that satisfies the predicate'), } as const satisfies Record ValidationError> const gteErrorMessage = (type: string) => (x: number | bigint, got: unknown) => 'Expected ' + type + ' to be greater than or equal to ' + x + ', got: ' + globalThis.String(got) diff --git a/packages/derive-validators/src/schemas/of.ts b/packages/derive-validators/src/schemas/of.ts new file mode 100644 index 00000000..c6557c41 --- /dev/null +++ b/packages/derive-validators/src/schemas/of.ts @@ -0,0 +1,14 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(inlineSchema: t.of): validate { + validateInline.tag = URI.inline + function validateInline(u: unknown, path = Array.of()) { + return inlineSchema(u) || [NullaryErrors.inline(u, path)] + } + return validateInline +} + diff --git a/packages/schema-core/src/schemas/of.ts b/packages/schema-core/src/schemas/of.ts index 3ee3933b..c02a9210 100644 --- a/packages/schema-core/src/schemas/of.ts +++ b/packages/schema-core/src/schemas/of.ts @@ -23,6 +23,9 @@ export namespace of { export let userDefinitions: Record = { //<%= Definitions %> } + export let userExtensions: Record = { + //<%= Extensions %> + } export function def(guard: T): of /* v8 ignore next 6 */ export function def(guard: T) { diff --git a/packages/schema-generator/src/imports.ts b/packages/schema-generator/src/imports.ts index d4f7b1a8..572de9e9 100644 --- a/packages/schema-generator/src/imports.ts +++ b/packages/schema-generator/src/imports.ts @@ -83,9 +83,13 @@ export function makeImport(dependency: string, { term, type }: ParsedImports, ma let getDependenciesFromImportsForSchema = (schemaExtensions: ExtensionsBySchemaName[keyof ExtensionsBySchemaName]) => { if (!schemaExtensions) return [] else { + + // console.log('\n\nschemaExtensions\n', JSON.stringify(schemaExtensions, null, 2), '\n') + let xs = Object.values(schemaExtensions) .filter((_) => !!_) - .flatMap((_) => Object.keys(_).filter((_) => _.startsWith('@traversable/'))) + .flatMap((_) => Object.keys(_)) + // .flatMap((_) => Object.keys(_).filter((_) => _.startsWith('@traversable/'))) return Array.from(new Set(xs)) } } @@ -99,6 +103,8 @@ export function deduplicateImports(extensionsBySchemaName: ExtensionsBySchemaNam let init: Record = {} let pkgNames = getDependenciesFromImportsForSchema(extension) + console.log('pkgNames', pkgNames) + for (let pkgName of pkgNames) { init[pkgName] = { type: { @@ -116,7 +122,10 @@ export function deduplicateImports(extensionsBySchemaName: ExtensionsBySchemaNam if (!imports) return {} fn.map(imports, (imports, pkgName) => { - if (!`${pkgName}`.startsWith('@traversable/')) return {} + + // console.log('imports', imports) + + // if (!`${pkgName}`.startsWith('@traversable/')) return {} if (!imports) return {} let { type, term } = imports diff --git a/packages/schema/src/schemas/void/toJsonSchema.ts b/packages/schema-to-json-schema/src/schemas/of.ts similarity index 62% rename from packages/schema/src/schemas/void/toJsonSchema.ts rename to packages/schema-to-json-schema/src/schemas/of.ts index d636b569..c8aaf62b 100644 --- a/packages/schema/src/schemas/void/toJsonSchema.ts +++ b/packages/schema-to-json-schema/src/schemas/of.ts @@ -1,7 +1,7 @@ export interface toJsonSchema { (): void } export function toJsonSchema(): toJsonSchema { - function voidToJsonSchema(): void { + function inlineToJsonSchema(): void { return void 0 } - return voidToJsonSchema + return inlineToJsonSchema } diff --git a/packages/schema/src/schemas/unknown/toString.ts b/packages/schema-to-string/src/schemas/of.ts similarity index 100% rename from packages/schema/src/schemas/unknown/toString.ts rename to packages/schema-to-string/src/schemas/of.ts diff --git a/packages/schema/src/_namespace.ts b/packages/schema/src/_namespace.ts index bcd12d4f..866430d6 100644 --- a/packages/schema/src/_namespace.ts +++ b/packages/schema/src/_namespace.ts @@ -1,32 +1,36 @@ export type { Entry, FirstOptionalItem, + IntersectType, Guard, Guarded, invalid, + Optional, + Required, Schema, SchemaLike, TupleType, ValidateTuple, } from '@traversable/schema-core/namespace' -export { any } from './schemas/any/core.js' -export { array } from './schemas/array/core.js' -export { bigint } from './schemas/bigint/core.js' -export { boolean } from './schemas/boolean/core.js' -export { eq } from './schemas/eq/core.js' -export { integer } from './schemas/integer/core.js' -export { intersect } from './schemas/intersect/core.js' -export { never } from './schemas/never/core.js' -export { null } from './schemas/null/core.js' -export { number } from './schemas/number/core.js' -export { object } from './schemas/object/core.js' -export { optional } from './schemas/optional/core.js' -export { record } from './schemas/record/core.js' -export { string } from './schemas/string/core.js' -export { symbol } from './schemas/symbol/core.js' -export { tuple } from './schemas/tuple/core.js' -export { undefined } from './schemas/undefined/core.js' -export { union } from './schemas/union/core.js' -export { unknown } from './schemas/unknown/core.js' -export { void } from './schemas/void/core.js' +export { any } from './schemas/any.js' +export { array } from './schemas/array.js' +export { bigint } from './schemas/bigint.js' +export { boolean } from './schemas/boolean.js' +export { eq } from './schemas/eq.js' +export { integer } from './schemas/integer.js' +export { intersect } from './schemas/intersect.js' +export { of } from './schemas/of.js' +export { never } from './schemas/never.js' +export { null } from './schemas/null.js' +export { number } from './schemas/number.js' +export { object } from './schemas/object.js' +export { optional } from './schemas/optional.js' +export { record } from './schemas/record.js' +export { string } from './schemas/string.js' +export { symbol } from './schemas/symbol.js' +export { tuple } from './schemas/tuple.js' +export { undefined } from './schemas/undefined.js' +export { union } from './schemas/union.js' +export { unknown } from './schemas/unknown.js' +export { void } from './schemas/void.js' diff --git a/packages/schema/src/build.ts b/packages/schema/src/build.ts index 37d9dde3..dd6959e0 100755 --- a/packages/schema/src/build.ts +++ b/packages/schema/src/build.ts @@ -1,15 +1,47 @@ #!/usr/bin/env pnpm dlx tsx import * as path from 'node:path' import * as fs from 'node:fs' -import type { pick, omit, Returns, IfReturns } from '@traversable/registry' +import type { IfReturns } from '@traversable/registry' import { fn } from '@traversable/registry' import { t } from '@traversable/schema-core' +import { generateSchemas } from '@traversable/schema-generator' + +/** + * ## TODO + * + * - [x] Pull the .ts files out of `@traversable/schema-core` + * - [x] Pull the .ts files out of `@traversable/derive-equals` + * - [x] Pull the .ts files out of `@traversable/schema-to-json-schema` + * - [ ] Pull the .ts files out of `@traversable/derive-validators` + * - [ ] Pull the .ts files out of `@traversable/schema-to-string` + */ + +let CWD = process.cwd() let PATH = { - sourcesDir: path.join(path.resolve(), 'node_modules', '@traversable'), - target: path.join(path.resolve(), 'src', 'schemas'), + libsDir: path.join(CWD, 'node_modules', '@traversable'), + tempDir: path.join(CWD, 'src', 'temp'), + extensionsDir: path.join(CWD, 'src', 'extensions'), + targetDir: path.join(CWD, 'src', 'schemas'), } +let EXTENSION_FILES_IGNORE_LIST = [ + 'equals.ts', + 'toJsonSchema.ts', + 'toString.ts', + 'validate.ts', +] + +/** + * TODO: Derive this list from the {@link EXTENSION_FILES_IGNORE_LIST ignore list} + */ +let REMOVE_IMPORTS_LIST = [ + /.*equals.js'\n/, + /.*toJsonSchema.js'\n/, + /.*toString.js'\n/, + /.*validate.js'\n/, +] + type Library = typeof Library[keyof typeof Library] let Library = { Core: 'schema-core', @@ -19,36 +51,6 @@ let Library = { Validators: 'derive-validators', } as const -let SCHEMA_WHITELIST = [ - 'of', - 'eq', - // - 'never', - 'any', - 'unknown', - 'void', - 'null', - 'undefined', - 'symbol', - 'boolean', - 'integer', - 'bigint', - 'number', - 'string', - // - 'optional', - 'array', - 'record', - 'union', - 'intersect', - 'tuple', - 'object', -] - -let LIB_BLACKLIST = [ - 'registry', -] satisfies any[] - let LIB_NAME_TO_TARGET_FILENAME = { [Library.Core]: 'core', [Library.Equals]: 'equals', @@ -57,8 +59,14 @@ let LIB_NAME_TO_TARGET_FILENAME = { [Library.ToString]: 'toString', } as const satisfies Record -let RelativeImport = { - namespace: { +let removeIgnoredImports = (content: string) => { + for (let ignore of REMOVE_IMPORTS_LIST) + content = content.replace(ignore, '') + return content +} + +let TargetReplace = { + internal: { /** * @example * // from: @@ -67,32 +75,35 @@ let RelativeImport = { * import type { Guarded, Schema, SchemaLike } from '../namespace.js' */ from: /'(\.\.\/)namespace.js'/g, - to: '\'../../_namespace.js\'', + to: '\'../_namespace.js\'', }, - local: (libShortName: string) => ({ - - from: /'\.\/(.+).js'/g, - to: (_: string, p1: string, p2: string) => `'../${p1}/${libShortName}'`, - - }), - parent: { + namespace: { from: /'@traversable\/schema-core'/g, - to: '\'../../_exports.js\'', + to: '\'../_exports.js\'', }, + coverageDirective: { + from: /\s*\/\* v8 ignore .+ \*\//g, + to: '', + } } +type Rewrite = (x: string) => string +let rewriteCoreInternalImport: Rewrite = (_) => _.replaceAll(TargetReplace.internal.from, TargetReplace.internal.to) +let rewriteCoreNamespaceImport: Rewrite = (_) => _.replaceAll(TargetReplace.namespace.from, TargetReplace.namespace.to) +let removeCoverageDirectives: Rewrite = (_) => _.replaceAll(TargetReplace.coverageDirective.from, TargetReplace.coverageDirective.to) + let isKeyOf = (k: keyof any, t: T): k is keyof T => !!t && (typeof t === 'object' || typeof t === 'function') && k in t - type GetTargetFileName = (libName: string, schemaName: string) => `${string}.ts` -type PostProcessor = (sourceFileContent: string, libConfig: LibOptions & { targetFileName: string }) => string +type PostProcessor = (sourceFileContent: string) => string type LibOptions = t.typeof let LibOptions = t.object({ relativePath: t.string, getTargetFileName: (x): x is GetTargetFileName => typeof x === 'function', - postprocessor: (x): x is PostProcessor => typeof x === 'function', + // tempPostProcessor: (x): x is PostProcessor => typeof x === 'function', + postProcessor: (x): x is PostProcessor => typeof x === 'function', // TODO: actually exclude files excludeFiles: t.array(t.string), includeFiles: t.optional(t.array(t.string)), @@ -102,7 +113,9 @@ type BuildOptions = t.typeof let BuildOptions = t.object({ dryRun: t.optional(t.boolean), getSourceDir: t.optional((x): x is (() => string) => typeof x === 'function'), + getTempDir: t.optional((x): x is (() => string) => typeof x === 'function'), getTargetDir: t.optional((x): x is (() => string) => typeof x === 'function'), + getExtensionFilesDir: t.optional((x): x is (() => string) => typeof x === 'function'), }) type LibsOptions = never | { libs: Record> } @@ -124,32 +137,19 @@ let defaultGetTargetFileName = ( : `${libName}.ts` ) satisfies LibOptions['getTargetFileName'] -let defaultPostProcessor = ( - (sourceFileContent, { targetFileName }) => { - let replaceLocalImports = RelativeImport.local(targetFileName) - return fn.pipe( - sourceFileContent, - (_) => _.replaceAll( - RelativeImport.namespace.from, - RelativeImport.namespace.to, - ), - (_) => _.replaceAll( - replaceLocalImports.from, - replaceLocalImports.to, - ), - (_) => _.replaceAll( - RelativeImport.parent.from, - RelativeImport.parent.to, - ), - ) - } -) satisfies PostProcessor +let defaultPostProcessor = (_: string) => fn.pipe( + _, + rewriteCoreInternalImport, + rewriteCoreNamespaceImport, + removeCoverageDirectives, + removeIgnoredImports, +) let defaultLibOptions = { relativePath: 'src/schemas', excludeFiles: [], getTargetFileName: defaultGetTargetFileName, - postprocessor: defaultPostProcessor, + postProcessor: defaultPostProcessor, } satisfies LibOptions let defaultLibs = { @@ -162,8 +162,10 @@ let defaultLibs = { let defaultOptions = { dryRun: false, - getSourceDir: () => path.join(process.cwd(), 'node_modules', '@traversable'), - getTargetDir: () => path.join(process.cwd(), 'src', 'schemas'), + getSourceDir: () => PATH.libsDir, + getTempDir: () => PATH.tempDir, + getTargetDir: () => PATH.targetDir, + getExtensionFilesDir: () => PATH.extensionsDir, libs: defaultLibs, } satisfies Required & LibsOptions @@ -171,14 +173,14 @@ function parseLibOptions({ excludeFiles = defaultLibOptions.excludeFiles, relativePath = defaultLibOptions.relativePath, getTargetFileName = defaultLibOptions.getTargetFileName, - postprocessor = defaultLibOptions.postprocessor, + postProcessor = defaultLibOptions.postProcessor, includeFiles, }: Partial): LibOptions { return { excludeFiles, relativePath, getTargetFileName, - postprocessor, + postProcessor, ...includeFiles && { includeFiles } } } @@ -186,43 +188,50 @@ function parseLibOptions({ function parseOptions(options: Options): Config function parseOptions({ getSourceDir = defaultOptions.getSourceDir, + getTempDir = defaultOptions.getTempDir, getTargetDir = defaultOptions.getTargetDir, + getExtensionFilesDir = defaultOptions.getExtensionFilesDir, dryRun = defaultOptions.dryRun, libs, }: Options = defaultOptions): Config { return { dryRun, + tempDir: getTempDir(), sourceDir: getSourceDir(), targetDir: getTargetDir(), + extensionFilesDir: getExtensionFilesDir(), libs: fn.map(libs, parseLibOptions), } } let tap - : (msg?: string, stringify?: (x: unknown) => string) => (x: T,) => T - = (msg, stringify) => (x) => ( - console.log('\n' + (msg ? `${msg}:\n` : '') + (stringify ? stringify(x) : x) + '\r'), - x - ) - -function ensureDirExists(cache: Set, $: Config) { - return (schemaFile: fs.Dirent) => { - let targetDirPath = path.join( - PATH.target, - schemaFile.name.slice(0, -'.ts'.length), - ) - if (!cache.has(targetDirPath) && !$.dryRun) { - cache.add(targetDirPath) - if (!fs.existsSync(targetDirPath)) { - fs.mkdirSync(targetDirPath) - } - } - return schemaFile + : (effect: (s: S) => T) => (x: S) => S + = (effect) => (x) => (effect(x), x) + +let ensureDir + : (dirpath: string) => void + = (dirpath) => !fs.existsSync(dirpath) && fs.mkdirSync(dirpath) + +function copyExtensionFiles($: Config) { + if (!fs.existsSync($.extensionFilesDir)) { + throw Error('Could not find extensions dir: ' + $.extensionFilesDir) } + let filenames = fs + .readdirSync($.extensionFilesDir) + .filter((filename) => !EXTENSION_FILES_IGNORE_LIST.includes(filename)) + + filenames.forEach((filename) => { + let tempDirName = filename.slice(0, -'.ts'.length) + let tempDirPath = path.join($.tempDir, tempDirName) + let tempPath = path.join(tempDirPath, 'extension.ts') + let sourcePath = path.join($.extensionFilesDir, filename) + let content = fs.readFileSync(sourcePath).toString('utf8') + ensureDir(tempDirPath) + fs.writeFileSync(tempPath, content) + }) } -function buildCoreSchemas(options: Options) { - let $ = parseOptions(options) +function buildSchemas($: Config) { let cache = new Set() return fs.readdirSync( @@ -243,206 +252,95 @@ function buildCoreSchemas(options: Options) { (schemaFile) => { let sourceFilePath = path.join(schemaFile.parentPath, schemaFile.name) let targetFileName = LIB.getTargetFileName(LIB_NAME, schemaFile.name) - let targetDirName = schemaFile.name.endsWith('.ts') ? schemaFile.name.slice(0, -'.ts'.length) : schemaFile.name + let tempDirName = schemaFile.name.endsWith('.ts') + ? schemaFile.name.slice(0, -'.ts'.length) + : schemaFile.name + let targetFilePath = path.join( - $.targetDir, - targetDirName, + $.tempDir, + tempDirName, targetFileName ) + let tempDirPath = path.join( + $.tempDir, + schemaFile.name.slice(0, -'.ts'.length), + ) + + if (!cache.has(tempDirPath) && !$.dryRun) { + cache.add(tempDirPath) + ensureDir(tempDirPath) + } + return fn.pipe( - schemaFile, - ensureDirExists(cache, $), - (schemaFile) => { - return [ - targetFilePath, - fs.readFileSync(sourceFilePath).toString('utf8'), - ] satisfies [any, any] - }, - ([targetFilePath, sourceFileContent]) => [ + [ targetFilePath, - LIB.postprocessor(sourceFileContent, { ...LIB, targetFileName }) + fs.readFileSync(sourceFilePath).toString('utf8') ] satisfies [any, any], - ([targetFilePath, content]) => ( - void ((!$.dryRun && fs.writeFileSync(targetFilePath, content))), - [targetFilePath, content] - ) + tap(([targetFilePath, content]) => fs.writeFileSync( + targetFilePath, + content, + )), ) } ), - // fn.map( - // fn.flow( - // ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], - // ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local(LIB_NAME).from, RelativeImport.local(LIB_NAME).to)] satisfies [any, any], - // ([filePath, content]) => (void (!$.dryRun && fs.writeFileSync(filePath, content)), [filePath, content]), - // ) - // ) - // tap('got em?') ) - }) + } + ) } -// (schemaFiles) => { -// schemaFiles.forEach((schemaFile) => { -// let targetDirPath = path.join( -// PATH.target, -// schemaFile.name.slice(0, -'.ts'.length), -// ) -// if (!cache.has(targetDirPath)) { -// cache.add(targetDirPath) -// if (!$.dryRun) { -// if (!fs.existsSync(targetDirPath)) { -// fs.mkdirSync(targetDirPath) -// } -// } -// else { -// console.group('\n\r[DRY_RUN]\r') -// console.log('[DEBUG]: !cache.has("' + targetDirPath + '")') -// if (!fs.existsSync(targetDirPath)) -// console.log('[DEBUG]: fs.mkdirSync("' + targetDirPath + '")') -// else -// console.log('[DEBUG]: fs.existsSync("' + targetDirPath + '")') -// console.groupEnd() -// } -// } else { -// if ($.dryRun) { -// console.group('\n\r[DRY_RUN]\r') -// console.log('[DEBUG]: cache.has("' + targetDirPath + '")') -// console.groupEnd() -// } - -// } -// }) -// return schemaFiles -// }, - -// (schemaFiles) => schemaFiles.map(({ name: schemaFileName, parentPath }) => { -// let schemaName = schemaFileName.slice(0, -'.ts'.length) - -// if (!sKeyOf(sourceDir.name, LIB_NAME_TO_FILENAME)) throw Error('dirName ' + dirName + ' is not a key of LIB_NAME_TO_FILENAME') -// else { -// return [ -// path.join( -// PATH.target, -// dirName.slice(0, -'.ts'.length), -// isKeyOf(dirName, LIB_NAME_TO_FILENAME) ? LIB_NAME_TO_FILENAME[dirName] + '.ts' : 'BORKED' -// ), -// fs.readFileSync( -// path.join( -// parentPath, -// dirName, -// ) -// ).toString('utf8') -// ] satisfies [any, any] -// } - -// }, -// ) - -// else { -// console.group('\n\r[DRY_RUN]\r') -// console.log('[DEBUG]: !cache.has("' + targetDirPath + '")') -// if (!fs.existsSync(targetDirPath)) -// console.log('[DEBUG]: fs.mkdirSync("' + targetDirPath + '")') -// else -// console.log('[DEBUG]: fs.existsSync("' + targetDirPath + '")') -// console.groupEnd() -// } -// } else { -// if ($.dryRun) { -// console.group('\n\r[DRY_RUN]\r') -// console.log('[DEBUG]: cache.has("' + targetDirPath + '")') -// console.groupEnd() -// } - - -// (schemaPaths) => schemaPaths.map(({ name: schemaFileName, parentPath }) => { -// let schemaName = schemaFileName.slice(0, -'.ts'.length) -// if (!isKeyOf(dirName, LIB_NAME_TO_FILENAME)) throw Error('dirName ' + dirName + ' is not a key of LIB_NAME_TO_FILENAME') -// else { -// return [ -// path.join( -// PATH.target, -// dirName.slice(0, -'.ts'.length), -// isKeyOf(dirName, LIB_NAME_TO_FILENAME) ? LIB_NAME_TO_FILENAME[dirName] + '.ts' : 'BORKED' -// ), -// fs.readFileSync( -// path.join( -// parentPath, -// dirName, -// ) -// ).toString('utf8') -// ] satisfies [any, any] -// } -// }, -// fn.map( -// fn.flow( -// ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], -// ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local.from, RelativeImport.local.to)] satisfies [any, any], -// ([filePath, content]) => !$.dryRun ? fs.writeFileSync(filePath, content) : void 0 -// ) -// ) -// ) -// ) -// ) - -// function buildSchemaExtensions() { -// return fs.readdirSync(PATH.sourcesDir, { withFileTypes: true }) -// .filter(({ name }) => !LIB_BLACKLIST.includes(name) && LIB_WHITELIST.includes(name)) -// .map( -// ({ name, parentPath }) => fn.pipe( -// path.join( -// parentPath, -// name, -// 'src', -// 'schemas', -// ), -// (absolutePath) => fs.readdirSync(absolutePath, { withFileTypes: true }), -// (schemaPaths) => schemaPaths.map(({ name: schemaName, parentPath }) => [ -// path.join( -// PATH.target, -// schemaName.slice(0, -'.ts'.length), -// isKeyOf(name, LIB_NAME_TO_FILENAME) ? LIB_NAME_TO_FILENAME[name] + '.ts' : 'BORKED', -// ), -// fs.readFileSync( -// path.join( -// parentPath, -// schemaName, -// ) -// ).toString('utf8') -// ] satisfies [any, any]), -// fn.map( -// fn.flow( -// ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.namespace.from, RelativeImport.namespace.to)] satisfies [any, any], -// ([filePath, content]) => [filePath, content.replaceAll(RelativeImport.local.from, RelativeImport.local.to)] satisfies [any, any], -// ([filePath, content]) => fs.writeFileSync(filePath, content)), -// ), -// ) -// ) -// } - -function ensureTargetExists() { - if (!fs.existsSync(PATH.target)) { - fs.mkdirSync(PATH.target) +function getSourcePaths($: Config) { + if (!fs.existsSync($.tempDir)) { + throw Error('[getSourcePaths] Expected temp directory to exist: ' + $.tempDir) } + + return fs.readdirSync($.tempDir, { withFileTypes: true }) + .reduce( + (acc, { name, parentPath }) => ({ + ...acc, + [name]: fs + .readdirSync(path.join(parentPath, name), { withFileTypes: true }) + .reduce( + (acc, { name, parentPath }) => ({ + ...acc, + [name.slice(0, -'.ts'.length)]: path.join(parentPath, name) + }), + {} + ) + }), + {} + ) } -function buildSchemas(options: Options) { - ensureTargetExists() - return buildCoreSchemas(options) - // buildSchemaExtensions() +function createTargetPaths($: Config, sourcePaths: Record>) { + return fn.map(sourcePaths, (_, schemaName) => path.join($.targetDir, `${schemaName}.ts`)) } -let out = buildSchemas(defaultOptions) +export function writeSchemas($: Config, sources: Record>, targets: Record): void { + let schemas = generateSchemas(sources, targets) + for (let [target, content] of schemas) { + void fs.writeFileSync(target, defaultPostProcessor(content)) + } +} -// console.log('out', out) +function build(options: Options) { + let $ = parseOptions(options) + void ensureDir($.tempDir) + void copyExtensionFiles($) + buildSchemas($) -/** - * ## TODO - * - * - [x] Pull the .ts files out of `@traversable/schema-core` - * - [x] Pull the .ts files out of `@traversable/derive-equals` - * - [x] Pull the .ts files out of `@traversable/schema-to-json-schema` - * - [ ] Pull the .ts files out of `@traversable/derive-validators` - * - [ ] Pull the .ts files out of `@traversable/schema-to-string` - */ + let sources = getSourcePaths($) + let targets = createTargetPaths($, sources) + + if ($.dryRun) return { + sources, + targets, + } + else { + void ensureDir($.targetDir) + void writeSchemas($, sources, targets) + void fs.rmSync($.tempDir, { force: true, recursive: true }) + } +} + +build(defaultOptions) diff --git a/packages/schema/src/extensions/any.ts b/packages/schema/src/extensions/any.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema/src/extensions/any.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/extensions/array.ts b/packages/schema/src/extensions/array.ts new file mode 100644 index 00000000..0927d4dd --- /dev/null +++ b/packages/schema/src/extensions/array.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toJsonSchema, + validate, + toString, + equals, +} diff --git a/packages/schema/src/extensions/bigint.ts b/packages/schema/src/extensions/bigint.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema/src/extensions/bigint.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/extensions/boolean.ts b/packages/schema/src/extensions/boolean.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema/src/extensions/boolean.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/extensions/eq.ts b/packages/schema/src/extensions/eq.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema/src/extensions/eq.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema/src/extensions/equals.ts b/packages/schema/src/extensions/equals.ts new file mode 100644 index 00000000..013153b1 --- /dev/null +++ b/packages/schema/src/extensions/equals.ts @@ -0,0 +1,3 @@ +export { dummyEquals as equals } +let dummyEquals = (..._: any) => { throw Error('Called dummy equals') } +interface dummyEquals<_ = any> { } diff --git a/packages/schema/src/extensions/integer.ts b/packages/schema/src/extensions/integer.ts new file mode 100644 index 00000000..1f68a7e8 --- /dev/null +++ b/packages/schema/src/extensions/integer.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toString, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema/src/extensions/intersect.ts b/packages/schema/src/extensions/intersect.ts new file mode 100644 index 00000000..0927d4dd --- /dev/null +++ b/packages/schema/src/extensions/intersect.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toJsonSchema, + validate, + toString, + equals, +} diff --git a/packages/schema/src/extensions/never.ts b/packages/schema/src/extensions/never.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema/src/extensions/never.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/extensions/null.ts b/packages/schema/src/extensions/null.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema/src/extensions/null.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/extensions/number.ts b/packages/schema/src/extensions/number.ts new file mode 100644 index 00000000..1a06ace0 --- /dev/null +++ b/packages/schema/src/extensions/number.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let Definitions = { + toString, + equals, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema/src/extensions/object.ts b/packages/schema/src/extensions/object.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema/src/extensions/object.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema/src/extensions/of.ts b/packages/schema/src/extensions/of.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema/src/extensions/of.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/extensions/optional.ts b/packages/schema/src/extensions/optional.ts new file mode 100644 index 00000000..0e7c4478 --- /dev/null +++ b/packages/schema/src/extensions/optional.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} + diff --git a/packages/schema/src/extensions/record.ts b/packages/schema/src/extensions/record.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema/src/extensions/record.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema/src/extensions/string.ts b/packages/schema/src/extensions/string.ts new file mode 100644 index 00000000..c64c1266 --- /dev/null +++ b/packages/schema/src/extensions/string.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + toString, + equals, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema/src/extensions/symbol.ts b/packages/schema/src/extensions/symbol.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema/src/extensions/symbol.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/extensions/toJsonSchema.ts b/packages/schema/src/extensions/toJsonSchema.ts new file mode 100644 index 00000000..16341d68 --- /dev/null +++ b/packages/schema/src/extensions/toJsonSchema.ts @@ -0,0 +1,3 @@ +export { dummyToJsonSchema as toJsonSchema } +let dummyToJsonSchema = (..._: any) => { throw Error('Called dummy toJsonSchema') } +interface dummyToJsonSchema<_ = any> { } diff --git a/packages/schema/src/extensions/toString.ts b/packages/schema/src/extensions/toString.ts new file mode 100644 index 00000000..d7215f1f --- /dev/null +++ b/packages/schema/src/extensions/toString.ts @@ -0,0 +1,3 @@ +export { dummyToString as toString } +let dummyToString = (..._: any) => { throw Error('Called dummy toString') } +interface dummyToString<_ = any> { } diff --git a/packages/schema/src/extensions/tuple.ts b/packages/schema/src/extensions/tuple.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema/src/extensions/tuple.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema/src/extensions/undefined.ts b/packages/schema/src/extensions/undefined.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema/src/extensions/undefined.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/extensions/union.ts b/packages/schema/src/extensions/union.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema/src/extensions/union.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema/src/extensions/unknown.ts b/packages/schema/src/extensions/unknown.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema/src/extensions/unknown.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/extensions/validate.ts b/packages/schema/src/extensions/validate.ts new file mode 100644 index 00000000..ba09b9fe --- /dev/null +++ b/packages/schema/src/extensions/validate.ts @@ -0,0 +1,3 @@ +export { dummyValidate as validate } +let dummyValidate = (..._: any) => { throw Error('Called dummy validate') } +interface dummyValidate<_ = any> { } diff --git a/packages/schema/src/extensions/void.ts b/packages/schema/src/extensions/void.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema/src/extensions/void.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema/src/schemas/any.ts b/packages/schema/src/schemas/any.ts new file mode 100644 index 00000000..3bd32d8b --- /dev/null +++ b/packages/schema/src/schemas/any.ts @@ -0,0 +1,80 @@ +/** + * t.any schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: unknown, right: unknown): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return unknownToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'any' } +export function toString(): 'any' { return 'any' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(_?: t.unknown): validate { + validateUnknown.tag = URI.unknown + function validateUnknown() { return true as const } + return validateUnknown +} +/// validate /// +////////////////////// + +export { any_ as any } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface any_ extends any_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function AnySchema(src: unknown): src is any { return true } +AnySchema.tag = URI.any +AnySchema.def = void 0 as any + +const any_ = Object_assign( + AnySchema, + userDefinitions, +) as any_ + +Object_assign(any_, userExtensions) + +declare namespace any_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.any + _type: any + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/any/core.ts b/packages/schema/src/schemas/any/core.ts deleted file mode 100644 index 877f92af..00000000 --- a/packages/schema/src/schemas/any/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { any_ as any } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface any_ extends any_.core { - //<%= Types %> -} - -function AnySchema(src: unknown): src is any { return true } -AnySchema.tag = URI.any -AnySchema.def = void 0 as any - -const any_ = Object_assign( - AnySchema, - userDefinitions, -) as any_ - -Object_assign(any_, userExtensions) - -declare namespace any_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.any - _type: any - get def(): this['_type'] - } -} diff --git a/packages/schema/src/schemas/any/equals.ts b/packages/schema/src/schemas/any/equals.ts deleted file mode 100644 index 09d8da5f..00000000 --- a/packages/schema/src/schemas/any/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" - -export type equals = Equal -export function equals(left: unknown, right: unknown): boolean { - return Object_is(left, right) -} diff --git a/packages/schema/src/schemas/any/toJsonSchema.ts b/packages/schema/src/schemas/any/toJsonSchema.ts deleted file mode 100644 index 25336fc6..00000000 --- a/packages/schema/src/schemas/any/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } -export function toJsonSchema(): toJsonSchema { - function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } - return unknownToJsonSchema -} diff --git a/packages/schema/src/schemas/any/toString.ts b/packages/schema/src/schemas/any/toString.ts deleted file mode 100644 index f70aa050..00000000 --- a/packages/schema/src/schemas/any/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'any' } -export function toString(): 'any' { return 'any' } diff --git a/packages/schema/src/schemas/any/validate.ts b/packages/schema/src/schemas/any/validate.ts deleted file mode 100644 index a41facf6..00000000 --- a/packages/schema/src/schemas/any/validate.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(_?: t.unknown): validate { - validateUnknown.tag = URI.unknown - function validateUnknown() { return true as const } - return validateUnknown -} diff --git a/packages/schema/src/schemas/array.ts b/packages/schema/src/schemas/array.ts new file mode 100644 index 00000000..f061b8e0 --- /dev/null +++ b/packages/schema/src/schemas/array.ts @@ -0,0 +1,250 @@ +/** + * t.array schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type * as T from '@traversable/registry' +import type { + Bounds, + Equal, + Integer, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + array as arrayOf, + Array_isArray, + bindUserExtensions, + carryover, + has, + Math_max, + Math_min, + Number_isSafeInteger, + Object_assign, + Object_is, + URI, + within +} from '@traversable/registry' +import type { Guarded, Schema, SchemaLike } from '../_namespace.js' +import type { of } from './of.js' +import type { t } from '../_exports.js' +import type { SizeBounds } from '@traversable/schema-to-json-schema' +import { hasSchema } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { Errors, NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal + +export function equals(arraySchema: t.array): equals +export function equals(arraySchema: t.array): equals +export function equals({ def }: t.array<{ equals: Equal }>): Equal { + let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is + function arrayEquals(l: unknown[], r: unknown[]): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + let len = l.length + if (len !== r.length) return false + for (let ix = len; ix-- !== 0;) + if (!equals(l[ix], r[ix])) return false + return true + } else return false + } + return arrayEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): never | T.Force< + & { type: 'array', items: T.Returns } + & T.PickIfDefined + > +} + +export function toJsonSchema>(arraySchema: T): toJsonSchema +export function toJsonSchema(arraySchema: T): toJsonSchema +export function toJsonSchema( + { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, +): () => { + type: 'array' + items: unknown + minLength?: number + maxLength?: number +} { + function arrayToJsonSchema() { + let items = hasSchema(def) ? def.toJsonSchema() : def + let out = { + type: 'array' as const, + items, + minLength, + maxLength, + } + if (typeof minLength !== 'number') delete out.minLength + if (typeof maxLength !== 'number') delete out.maxLength + return out + } + return arrayToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType})[]` +} + +export function toString(arraySchema: t.array): toString +export function toString(arraySchema: t.array): toString +export function toString({ def }: { def: unknown }) { + function arrayToString() { + let body = ( + !!def + && typeof def === 'object' + && 'toString' in def + && typeof def.toString === 'function' + ) ? def.toString() + : '${string}' + return ('(' + body + ')[]') + } + return arrayToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = never | ValidationFn +export function validate(arraySchema: t.array): validate +export function validate(arraySchema: t.array): validate +export function validate( + { def: { validate = () => true }, minLength, maxLength }: t.array +) { + validateArray.tag = URI.array + function validateArray(u: unknown, path = Array.of()) { + if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] + let errors = Array.of() + if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) + if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) + for (let i = 0, len = u.length; i < len; i++) { + let y = u[i] + let results = validate(y, [...path, i]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateArray +} +/// validate /// +////////////////////// + +/** @internal */ +function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { + return Object_assign(function BoundedArraySchema(u: unknown) { + return Array_isArray(u) && within(bounds)(u.length) + }, carry, array(schema)) +} + +export interface array extends array.core { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export function array(schema: S, readonly: 'readonly'): readonlyArray +export function array(schema: S): array +export function array(schema: S): array>> +export function array(schema: S): array { + return array.def(schema) +} + +export namespace array { + export let userDefinitions: Record = { + } as array + export function def(x: S, prev?: array): array + export function def(x: S, prev?: unknown): array + export function def(x: S, prev?: array): array + export function def(x: unknown, prev?: unknown): {} { + let userExtensions: Record = { + toJsonSchema, + validate, + toString, + equals, + } + const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray + function ArraySchema(src: unknown) { return predicate(src) } + ArraySchema.tag = URI.array + ArraySchema.def = x + ArraySchema.min = function arrayMin(minLength: Min) { + return Object_assign( + boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), + { minLength }, + ) + } + ArraySchema.max = function arrayMax(maxLength: Max) { + return Object_assign( + boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), + { maxLength }, + ) + } + ArraySchema.between = function arrayBetween( + min: Min, + max: Max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max) + ) { + return Object_assign( + boundedArray(x, { gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) + } + if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength + if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength + Object_assign(ArraySchema, userDefinitions) + return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) + } +} + +export declare namespace array { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.array + get def(): S + _type: S['_type' & keyof S][] + minLength?: number + maxLength?: number + min>(minLength: Min): array.Min + max>(maxLength: Max): array.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> + } + type Min + = [Self] extends [{ maxLength: number }] + ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> + : array.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> + : array.max + ; + interface min extends array { minLength: Min } + interface max extends array { maxLength: Max } + interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } + type type = never | T +} + +export const readonlyArray: { + (schema: S): readonlyArray + (schema: S): readonlyArray> +} = array +export interface readonlyArray { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: ReadonlyArray +} diff --git a/packages/schema/src/schemas/array/core.ts b/packages/schema/src/schemas/array/core.ts deleted file mode 100644 index 3128d3cb..00000000 --- a/packages/schema/src/schemas/array/core.ts +++ /dev/null @@ -1,128 +0,0 @@ -import type { - Bounds, - Integer, - Unknown, -} from '@traversable/registry' -import { - Array_isArray, - array as arrayOf, - bindUserExtensions, - carryover, - within, - _isPredicate, - has, - Math_max, - Math_min, - Number_isSafeInteger, - Object_assign, - URI, -} from '@traversable/registry' - -import type { Guarded, Schema, SchemaLike } from '../../_namespace.js' - -import type { of } from '../of/core.ts' - -/** @internal */ -function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { - return Object_assign(function BoundedArraySchema(u: unknown) { - return Array_isArray(u) && within(bounds)(u.length) - }, carry, array(schema)) -} - -export interface array extends array.core { - //<%= Types %> -} - -export function array(schema: S, readonly: 'readonly'): readonlyArray -export function array(schema: S): array -export function array(schema: S): array>> -export function array(schema: S): array { - return array.def(schema) -} - -export namespace array { - export let userDefinitions: Record = { - //<%= Definitions %> - } as array - export function def(x: S, prev?: array): array - export function def(x: S, prev?: unknown): array - export function def(x: S, prev?: array): array - /* v8 ignore next 1 */ - export function def(x: unknown, prev?: unknown): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray - function ArraySchema(src: unknown) { return predicate(src) } - ArraySchema.tag = URI.array - ArraySchema.def = x - ArraySchema.min = function arrayMin(minLength: Min) { - return Object_assign( - boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), - { minLength }, - ) - } - ArraySchema.max = function arrayMax(maxLength: Max) { - return Object_assign( - boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), - { maxLength }, - ) - } - ArraySchema.between = function arrayBetween( - min: Min, - max: Max, - minLength = Math_min(min, max), - maxLength = Math_max(min, max) - ) { - return Object_assign( - boundedArray(x, { gte: minLength, lte: maxLength }), - { minLength, maxLength }, - ) - } - if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength - if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength - Object_assign(ArraySchema, userDefinitions) - return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) - } -} - -export declare namespace array { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.array - get def(): S - _type: S['_type' & keyof S][] - minLength?: number - maxLength?: number - min>(minLength: Min): array.Min - max>(maxLength: Max): array.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> - } - type Min - = [Self] extends [{ maxLength: number }] - ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> - : array.min - ; - type Max - = [Self] extends [{ minLength: number }] - ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> - : array.max - ; - interface min extends array { minLength: Min } - interface max extends array { maxLength: Max } - interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } - type type = never | T -} - -export const readonlyArray: { - (schema: S): readonlyArray - (schema: S): readonlyArray> -} = array -export interface readonlyArray { - (u: unknown): u is this['_type'] - tag: URI.array - def: S - _type: ReadonlyArray -} diff --git a/packages/schema/src/schemas/array/equals.ts b/packages/schema/src/schemas/array/equals.ts deleted file mode 100644 index c09f189a..00000000 --- a/packages/schema/src/schemas/array/equals.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { has, Array_isArray, Object_is } from '@traversable/registry' -import type { t } from '../../_exports.js' - -export type equals = never | Equal - -export function equals(arraySchema: t.array): equals -export function equals(arraySchema: t.array): equals -export function equals({ def }: t.array<{ equals: Equal }>): Equal { - let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is - function arrayEquals(l: unknown[], r: unknown[]): boolean { - if (Object_is(l, r)) return true - if (Array_isArray(l)) { - if (!Array_isArray(r)) return false - let len = l.length - if (len !== r.length) return false - for (let ix = len; ix-- !== 0;) - if (!equals(l[ix], r[ix])) return false - return true - } else return false - } - return arrayEquals -} - diff --git a/packages/schema/src/schemas/array/toJsonSchema.ts b/packages/schema/src/schemas/array/toJsonSchema.ts deleted file mode 100644 index 15462a4a..00000000 --- a/packages/schema/src/schemas/array/toJsonSchema.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { t } from '../../_exports.js' -import type * as T from '@traversable/registry' -import type { SizeBounds } from '@traversable/schema-to-json-schema' -import { hasSchema } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): never | T.Force< - & { type: 'array', items: T.Returns } - & T.PickIfDefined - > -} - -export function toJsonSchema>(arraySchema: T): toJsonSchema -export function toJsonSchema(arraySchema: T): toJsonSchema -export function toJsonSchema( - { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, -): () => { - type: 'array' - items: unknown - minLength?: number - maxLength?: number -} { - function arrayToJsonSchema() { - let items = hasSchema(def) ? def.toJsonSchema() : def - let out = { - type: 'array' as const, - items, - minLength, - maxLength, - } - if (typeof minLength !== 'number') delete out.minLength - if (typeof maxLength !== 'number') delete out.maxLength - return out - } - return arrayToJsonSchema -} diff --git a/packages/schema/src/schemas/array/toString.ts b/packages/schema/src/schemas/array/toString.ts deleted file mode 100644 index fba36c7d..00000000 --- a/packages/schema/src/schemas/array/toString.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { t } from '../../_exports.js' - -export interface toString { - /* @ts-expect-error */ - (): never | `(${ReturnType})[]` -} - -export function toString(arraySchema: t.array): toString -export function toString(arraySchema: t.array): toString -export function toString({ def }: { def: unknown }) { - function arrayToString() { - let body = ( - !!def - && typeof def === 'object' - && 'toString' in def - && typeof def.toString === 'function' - ) ? def.toString() - : '${string}' - return ('(' + body + ')[]') - } - return arrayToString -} diff --git a/packages/schema/src/schemas/array/validate.ts b/packages/schema/src/schemas/array/validate.ts deleted file mode 100644 index 6204c6d6..00000000 --- a/packages/schema/src/schemas/array/validate.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { URI } from '@traversable/registry' -import type { t } from '../../_exports.js' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { Errors, NullaryErrors } from '@traversable/derive-validators' - -export type validate = never | ValidationFn -export function validate(arraySchema: t.array): validate -export function validate(arraySchema: t.array): validate -export function validate( - { def: { validate = () => true }, minLength, maxLength }: t.array -) { - validateArray.tag = URI.array - function validateArray(u: unknown, path = Array.of()) { - if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] - let errors = Array.of() - if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) - if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) - for (let i = 0, len = u.length; i < len; i++) { - let y = u[i] - let results = validate(y, [...path, i]) - if (results === true) continue - else errors.push(...results) - } - return errors.length === 0 || errors - } - return validateArray -} diff --git a/packages/schema/src/schemas/bigint/core.ts b/packages/schema/src/schemas/bigint.ts similarity index 62% rename from packages/schema/src/schemas/bigint/core.ts rename to packages/schema/src/schemas/bigint.ts index f7d55261..949b7cb7 100644 --- a/packages/schema/src/schemas/bigint/core.ts +++ b/packages/schema/src/schemas/bigint.ts @@ -1,12 +1,57 @@ -import type { Bounds, Unknown } from '@traversable/registry' +/** + * t.bigint schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Bounds, Equal, Unknown } from '@traversable/registry' import { bindUserExtensions, carryover, Object_assign, + Object_is, URI, - withinBig as within, + withinBig as within } from '@traversable/registry' - +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' +import type { t } from '../_exports.js' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: bigint, right: bigint): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function bigintToJsonSchema(): void { + return void 0 + } + return bigintToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'bigint' } +export function toString(): 'bigint' { return 'bigint' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(bigIntSchema: S): validate { + validateBigInt.tag = URI.bigint + function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { + return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] + } + return validateBigInt +} +/// validate /// +////////////////////// + export { bigint_ as bigint } /** @internal */ @@ -19,15 +64,20 @@ function boundedBigInt(bounds: Bounds, carry?: {}): {} { } interface bigint_ extends bigint_.core { - //<%= Types %> + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate } export let userDefinitions: Record = { - //<%= Definitions %> + equals, + toJsonSchema, + toString, } export let userExtensions: Record = { - //<%= Extensions %> + validate, } function BigIntSchema(src: unknown) { return typeof src === 'bigint' } @@ -96,4 +146,3 @@ declare namespace bigint_ { interface max extends bigint_ { maximum: Max } interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } } - diff --git a/packages/schema/src/schemas/bigint/equals.ts b/packages/schema/src/schemas/bigint/equals.ts deleted file mode 100644 index 7c98bbf1..00000000 --- a/packages/schema/src/schemas/bigint/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: bigint, right: bigint): boolean { - return Object_is(left, right) -} diff --git a/packages/schema/src/schemas/bigint/toJsonSchema.ts b/packages/schema/src/schemas/bigint/toJsonSchema.ts deleted file mode 100644 index f6c7bc5b..00000000 --- a/packages/schema/src/schemas/bigint/toJsonSchema.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function bigintToJsonSchema(): void { - return void 0 - } - return bigintToJsonSchema -} diff --git a/packages/schema/src/schemas/bigint/toString.ts b/packages/schema/src/schemas/bigint/toString.ts deleted file mode 100644 index 793c903e..00000000 --- a/packages/schema/src/schemas/bigint/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'bigint' } -export function toString(): 'bigint' { return 'bigint' } diff --git a/packages/schema/src/schemas/bigint/validate.ts b/packages/schema/src/schemas/bigint/validate.ts deleted file mode 100644 index 4e563d80..00000000 --- a/packages/schema/src/schemas/bigint/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(bigIntSchema: S): validate { - validateBigInt.tag = URI.bigint - function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { - return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] - } - return validateBigInt -} diff --git a/packages/schema/src/schemas/boolean.ts b/packages/schema/src/schemas/boolean.ts new file mode 100644 index 00000000..5fe4c1cf --- /dev/null +++ b/packages/schema/src/schemas/boolean.ts @@ -0,0 +1,83 @@ +/** + * t.boolean schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: boolean, right: boolean): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'boolean' } } +export function toJsonSchema(): toJsonSchema { + function booleanToJsonSchema() { return { type: 'boolean' as const } } + return booleanToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'boolean' } +export function toString(): 'boolean' { return 'boolean' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(booleanSchema: t.boolean): validate { + validateBoolean.tag = URI.boolean + function validateBoolean(u: unknown, path = Array.of()) { + return booleanSchema(true as const) || [NullaryErrors.null(u, path)] + } + return validateBoolean +} +/// validate /// +////////////////////// + +export { boolean_ as boolean } + +interface boolean_ extends boolean_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } + +BooleanSchema.tag = URI.boolean +BooleanSchema.def = false + +const boolean_ = Object_assign( + BooleanSchema, + userDefinitions, +) as boolean_ + +Object_assign(boolean_, userExtensions) + +declare namespace boolean_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.boolean + _type: boolean + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/boolean/core.ts b/packages/schema/src/schemas/boolean/core.ts deleted file mode 100644 index c89adf4b..00000000 --- a/packages/schema/src/schemas/boolean/core.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { boolean_ as boolean } - -interface boolean_ extends boolean_.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } - -BooleanSchema.tag = URI.boolean -BooleanSchema.def = false - -const boolean_ = Object_assign( - BooleanSchema, - userDefinitions, -) as boolean_ - -Object_assign(boolean_, userExtensions) - -declare namespace boolean_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.boolean - _type: boolean - get def(): this['_type'] - } -} diff --git a/packages/schema/src/schemas/boolean/equals.ts b/packages/schema/src/schemas/boolean/equals.ts deleted file mode 100644 index 306bb12b..00000000 --- a/packages/schema/src/schemas/boolean/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: boolean, right: boolean): boolean { - return Object_is(left, right) -} diff --git a/packages/schema/src/schemas/boolean/toJsonSchema.ts b/packages/schema/src/schemas/boolean/toJsonSchema.ts deleted file mode 100644 index d1b86f70..00000000 --- a/packages/schema/src/schemas/boolean/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): { type: 'boolean' } } -export function toJsonSchema(): toJsonSchema { - function booleanToJsonSchema() { return { type: 'boolean' as const } } - return booleanToJsonSchema -} diff --git a/packages/schema/src/schemas/boolean/toString.ts b/packages/schema/src/schemas/boolean/toString.ts deleted file mode 100644 index 3c408e57..00000000 --- a/packages/schema/src/schemas/boolean/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'boolean' } -export function toString(): 'boolean' { return 'boolean' } diff --git a/packages/schema/src/schemas/boolean/validate.ts b/packages/schema/src/schemas/boolean/validate.ts deleted file mode 100644 index 89a3331b..00000000 --- a/packages/schema/src/schemas/boolean/validate.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(booleanSchema: t.boolean): validate { - validateBoolean.tag = URI.boolean - function validateBoolean(u: unknown, path = Array.of()) { - return booleanSchema(true as const) || [NullaryErrors.null(u, path)] - } - return validateBoolean -} diff --git a/packages/schema/src/schemas/eq.ts b/packages/schema/src/schemas/eq.ts new file mode 100644 index 00000000..60b5d209 --- /dev/null +++ b/packages/schema/src/schemas/eq.ts @@ -0,0 +1,119 @@ +/** + * t.eq schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Key, + Mut, + Mutable, + SchemaOptions as Options, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + applyOptions, + bindUserExtensions, + Equal, + getConfig, + Object_assign, + URI +} from '@traversable/registry' +import { t } from '../_exports.js' +import { stringify } from '@traversable/schema-to-string' +import type { Validate } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal +export function equals(eqSchema: t.eq): equals +export function equals(): Equal { + return (left: unknown, right: unknown) => t.eq(left)(right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { const: T } } +export function toJsonSchema(eqSchema: t.eq): toJsonSchema +export function toJsonSchema({ def }: t.eq) { + function eqToJsonSchema() { return { const: def } } + return eqToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): [Key] extends [never] + ? [T] extends [symbol] ? 'symbol' : 'symbol' + : [T] extends [string] ? `'${T}'` : Key +} + +export function toString(eqSchema: t.eq): toString +export function toString({ def }: t.eq): () => string { + function eqToString(): string { + return typeof def === 'symbol' ? 'symbol' : stringify(def) + } + return eqToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate +export function validate(eqSchema: t.eq): validate +export function validate({ def }: t.eq): validate { + validateEq.tag = URI.eq + function validateEq(u: unknown, path = Array.of()) { + let options = getConfig().schema + let equals = options?.eq?.equalsFn || Equal.lax + if (equals(def, u)) return true + else return [Errors.eq(u, path, def)] + } + return validateEq +} +/// validate /// +////////////////////// + +export function eq>(value: V, options?: Options): eq> +export function eq(value: V, options?: Options): eq +export function eq(value: V, options?: Options): eq { + return eq.def(value, options) +} + +export interface eq extends eq.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace eq { + export let userDefinitions: Record = { + } + export function def(value: T, options?: Options): eq + export function def(x: T, $?: Options): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const options = applyOptions($) + const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return predicate(src) } + EqSchema.tag = URI.eq + EqSchema.def = x + Object_assign(EqSchema, eq.userDefinitions) + return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) + } +} + +export declare namespace eq { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.eq + _type: V + get def(): V + } +} diff --git a/packages/schema/src/schemas/eq/core.ts b/packages/schema/src/schemas/eq/core.ts deleted file mode 100644 index d701a0f6..00000000 --- a/packages/schema/src/schemas/eq/core.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' -import { applyOptions, bindUserExtensions, _isPredicate, Object_assign, URI } from '@traversable/registry' - -export function eq>(value: V, options?: Options): eq> -export function eq(value: V, options?: Options): eq -export function eq(value: V, options?: Options): eq { - return eq.def(value, options) -} - -export interface eq extends eq.core { - //<%= Types %> -} - -export namespace eq { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(value: T, options?: Options): eq - /* v8 ignore next 1 */ - export function def(x: T, $?: Options): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const options = applyOptions($) - const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) - function EqSchema(src: unknown) { return predicate(src) } - EqSchema.tag = URI.eq - EqSchema.def = x - Object_assign(EqSchema, eq.userDefinitions) - return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) - } -} - -export declare namespace eq { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.eq - _type: V - get def(): V - } -} diff --git a/packages/schema/src/schemas/eq/equals.ts b/packages/schema/src/schemas/eq/equals.ts deleted file mode 100644 index 8b6f5552..00000000 --- a/packages/schema/src/schemas/eq/equals.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { t } from '../../_exports.js' - -export type equals = never | Equal -export function equals(eqSchema: t.eq): equals -export function equals(): Equal { - return (left: unknown, right: unknown) => t.eq(left)(right) -} diff --git a/packages/schema/src/schemas/eq/toJsonSchema.ts b/packages/schema/src/schemas/eq/toJsonSchema.ts deleted file mode 100644 index f5c5c089..00000000 --- a/packages/schema/src/schemas/eq/toJsonSchema.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { t } from '../../_exports.js' - -export interface toJsonSchema { (): { const: T } } -export function toJsonSchema(eqSchema: t.eq): toJsonSchema -export function toJsonSchema({ def }: t.eq) { - function eqToJsonSchema() { return { const: def } } - return eqToJsonSchema -} diff --git a/packages/schema/src/schemas/eq/toString.ts b/packages/schema/src/schemas/eq/toString.ts deleted file mode 100644 index 7d8c8fb5..00000000 --- a/packages/schema/src/schemas/eq/toString.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Key } from '@traversable/registry' -import type { t } from '../../_exports.js' -import { stringify } from '@traversable/schema-to-string' - -export interface toString { - (): [Key] extends [never] - ? [T] extends [symbol] ? 'symbol' : 'symbol' - : [T] extends [string] ? `'${T}'` : Key -} - -export function toString(eqSchema: t.eq): toString -export function toString({ def }: t.eq): () => string { - function eqToString(): string { - return typeof def === 'symbol' ? 'symbol' : stringify(def) - } - return eqToString -} diff --git a/packages/schema/src/schemas/eq/validate.ts b/packages/schema/src/schemas/eq/validate.ts deleted file mode 100644 index 91a59653..00000000 --- a/packages/schema/src/schemas/eq/validate.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Equal, getConfig, URI } from '@traversable/registry' -import type { t } from '../../_exports.js' -import type { Validate } from '@traversable/derive-validators' -import { Errors } from '@traversable/derive-validators' - -export type validate = Validate -export function validate(eqSchema: t.eq): validate -export function validate({ def }: t.eq): validate { - validateEq.tag = URI.eq - function validateEq(u: unknown, path = Array.of()) { - let options = getConfig().schema - let equals = options?.eq?.equalsFn || Equal.lax - if (equals(def, u)) return true - else return [Errors.eq(u, path, def)] - } - return validateEq -} diff --git a/packages/schema/src/schemas/integer/core.ts b/packages/schema/src/schemas/integer.ts similarity index 52% rename from packages/schema/src/schemas/integer/core.ts rename to packages/schema/src/schemas/integer.ts index 2969e5a1..684f132f 100644 --- a/packages/schema/src/schemas/integer/core.ts +++ b/packages/schema/src/schemas/integer.ts @@ -1,16 +1,80 @@ -import type { Bounds, Integer, Unknown } from '@traversable/registry' +/** + * t.integer schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Bounds, + Equal, + Force, + Integer, + PickIfDefined, + Unknown +} from '@traversable/registry' import { bindUserExtensions, carryover, - Math_min, Math_max, + Math_min, Number_isSafeInteger, Object_assign, + SameValueNumber, URI, - within, + within } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } - +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.integer): toJsonSchema { + function integerToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'integer' as const, + ...bounds, + } + } + return integerToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(integerSchema: S): validate { + validateInteger.tag = URI.integer + function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { + return integerSchema(u) || [NullaryErrors.integer(u, path)] + } + return validateInteger +} +/// validate /// +////////////////////// + export { integer } /** @internal */ @@ -23,15 +87,20 @@ function boundedInteger(bounds: Bounds, carry?: {}): {} { } interface integer extends integer.core { - //<%= Types %> + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate } export let userDefinitions: Record = { - //<%= Definitions %> + equals, + toString, } export let userExtensions: Record = { - //<%= Extensions %> + toJsonSchema, + validate, } function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } diff --git a/packages/schema/src/schemas/integer/equals.ts b/packages/schema/src/schemas/integer/equals.ts deleted file mode 100644 index 29fcd602..00000000 --- a/packages/schema/src/schemas/integer/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { SameValueNumber } from '@traversable/registry' - -export type equals = Equal -export function equals(left: number, right: number): boolean { - return SameValueNumber(left, right) -} diff --git a/packages/schema/src/schemas/integer/toJsonSchema.ts b/packages/schema/src/schemas/integer/toJsonSchema.ts deleted file mode 100644 index 1c2f6781..00000000 --- a/packages/schema/src/schemas/integer/toJsonSchema.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Force, PickIfDefined } from '@traversable/registry' -import type { t } from '../../_exports.js' -import type { NumericBounds } from '@traversable/schema-to-json-schema' -import { getNumericBounds } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: t.integer): toJsonSchema { - function integerToJsonSchema() { - const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) - let bounds: NumericBounds = {} - if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum - if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum - if (typeof minimum === 'number') bounds.minimum = minimum - if (typeof maximum === 'number') bounds.maximum = maximum - return { - type: 'integer' as const, - ...bounds, - } - } - return integerToJsonSchema -} diff --git a/packages/schema/src/schemas/integer/toString.ts b/packages/schema/src/schemas/integer/toString.ts deleted file mode 100644 index 912565e6..00000000 --- a/packages/schema/src/schemas/integer/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'number' } -export function toString(): 'number' { return 'number' } diff --git a/packages/schema/src/schemas/integer/validate.ts b/packages/schema/src/schemas/integer/validate.ts deleted file mode 100644 index 125ae847..00000000 --- a/packages/schema/src/schemas/integer/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(integerSchema: S): validate { - validateInteger.tag = URI.integer - function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { - return integerSchema(u) || [NullaryErrors.integer(u, path)] - } - return validateInteger -} diff --git a/packages/schema/src/schemas/intersect.ts b/packages/schema/src/schemas/intersect.ts new file mode 100644 index 00000000..c8c7f32c --- /dev/null +++ b/packages/schema/src/schemas/intersect.ts @@ -0,0 +1,147 @@ +/** + * t.intersect schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Equal, + Join, + Returns, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + intersect as intersect$, + isUnknown as isAny, + Object_assign, + Object_is, + URI +} from '@traversable/registry' +import type { + Entry, + IntersectType, + Schema, + SchemaLike +} from '../_namespace.js' +import type { t } from '../_exports.js' +import { getSchema } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals({ def }: t.intersect<{ equals: Equal }[]>): Equal { + function intersectEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (!def[ix].equals(l, r)) return false + return true + } + return intersectEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { + allOf: { [I in keyof T]: Returns } + } +} + +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema({ def }: t.intersect): () => {} { + function intersectToJsonSchema() { + return { + allOf: def.map(getSchema) + } + } + return intersectToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): never | [T] extends [readonly []] ? 'unknown' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` +} + +export function toString(intersectSchema: t.intersect): toString +export function toString({ def }: t.intersect): () => string { + function intersectToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' + } + return intersectToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate + +export function validate(intersectSchema: t.intersect): validate +export function validate(intersectSchema: t.intersect): validate +export function validate({ def }: t.intersect) { + validateIntersect.tag = URI.intersect + function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results !== true) + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + return errors.length === 0 || errors + } + return validateIntersect +} +/// validate /// +////////////////////// + +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: readonly unknown[]) { + return intersect.def(schemas) +} + +export interface intersect extends intersect.core { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export namespace intersect { + export let userDefinitions: Record = { + } as intersect + export function def(xs: readonly [...T]): intersect + export function def(xs: readonly unknown[]): {} { + let userExtensions: Record = { + toJsonSchema, + validate, + toString, + equals, + } + const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny + function IntersectSchema(src: unknown) { return predicate(src) } + IntersectSchema.tag = URI.intersect + IntersectSchema.def = xs + Object_assign(IntersectSchema, intersect.userDefinitions) + return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) + } +} + +export declare namespace intersect { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.intersect + get def(): S + _type: IntersectType + } + type type> = never | T +} diff --git a/packages/schema/src/schemas/intersect/core.ts b/packages/schema/src/schemas/intersect/core.ts deleted file mode 100644 index 47dd97da..00000000 --- a/packages/schema/src/schemas/intersect/core.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - _isPredicate, - bindUserExtensions, - intersect as intersect$, - isUnknown as isAny, - Object_assign, - URI, -} from '@traversable/registry' - -import type { Entry, IntersectType, Schema, SchemaLike } from '../../_namespace.js' - -export function intersect(...schemas: S): intersect -export function intersect }>(...schemas: S): intersect -export function intersect(...schemas: readonly unknown[]) { - return intersect.def(schemas) -} - -export interface intersect extends intersect.core { - //<%= Types %> -} - -export namespace intersect { - export let userDefinitions: Record = { - //<%= Definitions %> - } as intersect - export function def(xs: readonly [...T]): intersect - /* v8 ignore next 1 */ - export function def(xs: readonly unknown[]): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny - function IntersectSchema(src: unknown) { return predicate(src) } - IntersectSchema.tag = URI.intersect - IntersectSchema.def = xs - Object_assign(IntersectSchema, intersect.userDefinitions) - return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) - } -} - -export declare namespace intersect { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.intersect - get def(): S - _type: IntersectType - } - type type> = never | T -} diff --git a/packages/schema/src/schemas/intersect/equals.ts b/packages/schema/src/schemas/intersect/equals.ts deleted file mode 100644 index f1faa5b1..00000000 --- a/packages/schema/src/schemas/intersect/equals.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' -import type { t } from '../../_exports.js' - -export type equals = Equal -export function equals(intersectSchema: t.intersect<[...S]>): equals -export function equals(intersectSchema: t.intersect<[...S]>): equals -export function equals({ def }: t.intersect<{ equals: Equal }[]>): Equal { - function intersectEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - for (let ix = def.length; ix-- !== 0;) - if (!def[ix].equals(l, r)) return false - return true - } - return intersectEquals -} diff --git a/packages/schema/src/schemas/intersect/toJsonSchema.ts b/packages/schema/src/schemas/intersect/toJsonSchema.ts deleted file mode 100644 index 85ea91f8..00000000 --- a/packages/schema/src/schemas/intersect/toJsonSchema.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Returns } from '@traversable/registry' -import type { t } from '../../_exports.js' -import { getSchema } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): { - allOf: { [I in keyof T]: Returns } - } -} - -export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema -export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema -export function toJsonSchema({ def }: t.intersect): () => {} { - function intersectToJsonSchema() { - return { - allOf: def.map(getSchema) - } - } - return intersectToJsonSchema -} diff --git a/packages/schema/src/schemas/intersect/toString.ts b/packages/schema/src/schemas/intersect/toString.ts deleted file mode 100644 index 9c23db18..00000000 --- a/packages/schema/src/schemas/intersect/toString.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Join } from '@traversable/registry' -import { Array_isArray } from '@traversable/registry' -import type { t } from '../../_exports.js' -import { callToString } from '@traversable/schema-to-string' - -export interface toString { - (): never | [T] extends [readonly []] ? 'unknown' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` -} - -export function toString(intersectSchema: t.intersect): toString -export function toString({ def }: t.intersect): () => string { - function intersectToString() { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' - } - return intersectToString -} diff --git a/packages/schema/src/schemas/intersect/validate.ts b/packages/schema/src/schemas/intersect/validate.ts deleted file mode 100644 index 330eb28f..00000000 --- a/packages/schema/src/schemas/intersect/validate.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { URI } from '@traversable/registry' -import type { t } from '../../_exports.js' -import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' - -export type validate = Validate - -export function validate(intersectSchema: t.intersect): validate -export function validate(intersectSchema: t.intersect): validate -export function validate({ def }: t.intersect) { - validateIntersect.tag = URI.intersect - function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { - let errors = Array.of() - for (let i = 0; i < def.length; i++) { - let results = def[i].validate(u, path) - if (results !== true) - for (let j = 0; j < results.length; j++) errors.push(results[j]) - } - return errors.length === 0 || errors - } - return validateIntersect -} diff --git a/packages/schema/src/schemas/never.ts b/packages/schema/src/schemas/never.ts new file mode 100644 index 00000000..f3d36102 --- /dev/null +++ b/packages/schema/src/schemas/never.ts @@ -0,0 +1,80 @@ +/** + * t.never schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: never, right: never): boolean { + return false +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): never } +export function toJsonSchema(): toJsonSchema { + function neverToJsonSchema() { return void 0 as never } + return neverToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'never' } +export function toString(): 'never' { return 'never' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(_?: t.never): validate { + validateNever.tag = URI.never + function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } + return validateNever +} +/// validate /// +////////////////////// + +export { never_ as never } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface never_ extends never_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function NeverSchema(src: unknown): src is never { return false } +NeverSchema.tag = URI.never; +NeverSchema.def = void 0 as never + +const never_ = Object_assign( + NeverSchema, + userDefinitions, +) as never_ + +Object_assign(never_, userExtensions) + +export declare namespace never_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.never + _type: never + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/never/core.ts b/packages/schema/src/schemas/never/core.ts deleted file mode 100644 index a0077281..00000000 --- a/packages/schema/src/schemas/never/core.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { never_ as never } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface never_ extends never_.core { - //<%= Types %> -} - -function NeverSchema(src: unknown): src is never { return false } -NeverSchema.tag = URI.never; -NeverSchema.def = void 0 as never - -const never_ = Object_assign( - NeverSchema, - userDefinitions, -) as never_ - -Object_assign(never_, userExtensions) - -export declare namespace never_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.never - _type: never - get def(): this['_type'] - } -} - diff --git a/packages/schema/src/schemas/never/equals.ts b/packages/schema/src/schemas/never/equals.ts deleted file mode 100644 index 3ed89421..00000000 --- a/packages/schema/src/schemas/never/equals.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Equal } from '@traversable/registry' - -export type equals = Equal -export function equals(left: never, right: never): boolean { - return false -} diff --git a/packages/schema/src/schemas/never/toJsonSchema.ts b/packages/schema/src/schemas/never/toJsonSchema.ts deleted file mode 100644 index d22338df..00000000 --- a/packages/schema/src/schemas/never/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): never } -export function toJsonSchema(): toJsonSchema { - function neverToJsonSchema() { return void 0 as never } - return neverToJsonSchema -} diff --git a/packages/schema/src/schemas/never/toString.ts b/packages/schema/src/schemas/never/toString.ts deleted file mode 100644 index aaabf80d..00000000 --- a/packages/schema/src/schemas/never/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'never' } -export function toString(): 'never' { return 'never' } diff --git a/packages/schema/src/schemas/never/validate.ts b/packages/schema/src/schemas/never/validate.ts deleted file mode 100644 index b6658fa3..00000000 --- a/packages/schema/src/schemas/never/validate.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(_?: t.never): validate { - validateNever.tag = URI.never - function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } - return validateNever -} diff --git a/packages/schema/src/schemas/null.ts b/packages/schema/src/schemas/null.ts new file mode 100644 index 00000000..7d8ef404 --- /dev/null +++ b/packages/schema/src/schemas/null.ts @@ -0,0 +1,86 @@ +/** + * t.null schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: null, right: null): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'null', enum: [null] } } +export function toJsonSchema(): toJsonSchema { + function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } + return nullToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'null' } +export function toString(): 'null' { return 'null' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(nullSchema: t.null): validate { + validateNull.tag = URI.null + function validateNull(u: unknown, path = Array.of()) { + return nullSchema(u) || [NullaryErrors.null(u, path)] + } + return validateNull +} +/// validate /// +////////////////////// + +export { null_ as null, null_ } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface null_ extends null_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function NullSchema(src: unknown): src is null { return src === null } +NullSchema.def = null +NullSchema.tag = URI.null + +const null_ = Object_assign( + NullSchema, + userDefinitions, +) as null_ + +Object_assign( + null_, + userExtensions, +) + +declare namespace null_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.null + _type: null + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/null/core.ts b/packages/schema/src/schemas/null/core.ts deleted file mode 100644 index befebeb5..00000000 --- a/packages/schema/src/schemas/null/core.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { null_ as null, null_ } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface null_ extends null_.core { - //<%= Types %> -} - -function NullSchema(src: unknown): src is null { return src === null } -NullSchema.def = null -NullSchema.tag = URI.null - -const null_ = Object_assign( - NullSchema, - userDefinitions, -) as null_ - -Object_assign( - null_, - userExtensions, -) - -declare namespace null_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.null - _type: null - get def(): this['_type'] - } -} diff --git a/packages/schema/src/schemas/null/equals.ts b/packages/schema/src/schemas/null/equals.ts deleted file mode 100644 index 12c2f636..00000000 --- a/packages/schema/src/schemas/null/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: null, right: null): boolean { - return Object_is(left, right) -} diff --git a/packages/schema/src/schemas/null/toJsonSchema.ts b/packages/schema/src/schemas/null/toJsonSchema.ts deleted file mode 100644 index 7a3b7c3a..00000000 --- a/packages/schema/src/schemas/null/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): { type: 'null', enum: [null] } } -export function toJsonSchema(): toJsonSchema { - function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } - return nullToJsonSchema -} diff --git a/packages/schema/src/schemas/null/toString.ts b/packages/schema/src/schemas/null/toString.ts deleted file mode 100644 index 35c3aef8..00000000 --- a/packages/schema/src/schemas/null/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'null' } -export function toString(): 'null' { return 'null' } diff --git a/packages/schema/src/schemas/null/validate.ts b/packages/schema/src/schemas/null/validate.ts deleted file mode 100644 index 0049e964..00000000 --- a/packages/schema/src/schemas/null/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(nullSchema: t.null): validate { - validateNull.tag = URI.null - function validateNull(u: unknown, path = Array.of()) { - return nullSchema(u) || [NullaryErrors.null(u, path)] - } - return validateNull -} diff --git a/packages/schema/src/schemas/number/core.ts b/packages/schema/src/schemas/number.ts similarity index 65% rename from packages/schema/src/schemas/number/core.ts rename to packages/schema/src/schemas/number.ts index 5972ae85..123011e2 100644 --- a/packages/schema/src/schemas/number/core.ts +++ b/packages/schema/src/schemas/number.ts @@ -1,27 +1,95 @@ -import type { Bounds, Unknown } from '@traversable/registry' +/** + * t.number schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Bounds, + Equal, + Force, + PickIfDefined, + Unknown +} from '@traversable/registry' import { bindUserExtensions, carryover, - Math_min, Math_max, + Math_min, Object_assign, + SameValueNumber, URI, - within, + within } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } - +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.number): toJsonSchema { + function numberToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'number' as const, + ...bounds, + } + } + return numberToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(numberSchema: S): validate { + validateNumber.tag = URI.number + function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return numberSchema(u) || [NullaryErrors.number(u, path)] + } + return validateNumber +} +/// validate /// +////////////////////// + export { number_ as number } interface number_ extends number_.core { - //<%= Types %> + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate } export let userDefinitions: Record = { - //<%= Definitions %> + toString, + equals, } export let userExtensions: Record = { - //<%= Extensions %> + toJsonSchema, + validate, } function NumberSchema(src: unknown) { return typeof src === 'number' } diff --git a/packages/schema/src/schemas/number/equals.ts b/packages/schema/src/schemas/number/equals.ts deleted file mode 100644 index 29fcd602..00000000 --- a/packages/schema/src/schemas/number/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { SameValueNumber } from '@traversable/registry' - -export type equals = Equal -export function equals(left: number, right: number): boolean { - return SameValueNumber(left, right) -} diff --git a/packages/schema/src/schemas/number/toJsonSchema.ts b/packages/schema/src/schemas/number/toJsonSchema.ts deleted file mode 100644 index 7ca6d76e..00000000 --- a/packages/schema/src/schemas/number/toJsonSchema.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Force, PickIfDefined } from '@traversable/registry' -import type { t } from '../../_exports.js' -import type { NumericBounds } from '@traversable/schema-to-json-schema' -import { getNumericBounds } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: t.number): toJsonSchema { - function numberToJsonSchema() { - const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) - let bounds: NumericBounds = {} - if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum - if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum - if (typeof minimum === 'number') bounds.minimum = minimum - if (typeof maximum === 'number') bounds.maximum = maximum - return { - type: 'number' as const, - ...bounds, - } - } - return numberToJsonSchema -} diff --git a/packages/schema/src/schemas/number/toString.ts b/packages/schema/src/schemas/number/toString.ts deleted file mode 100644 index 912565e6..00000000 --- a/packages/schema/src/schemas/number/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'number' } -export function toString(): 'number' { return 'number' } diff --git a/packages/schema/src/schemas/number/validate.ts b/packages/schema/src/schemas/number/validate.ts deleted file mode 100644 index fbe5c398..00000000 --- a/packages/schema/src/schemas/number/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(numberSchema: S): validate { - validateNumber.tag = URI.number - function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { - return numberSchema(u) || [NullaryErrors.number(u, path)] - } - return validateNumber -} diff --git a/packages/schema/src/schemas/object.ts b/packages/schema/src/schemas/object.ts new file mode 100644 index 00000000..d092654c --- /dev/null +++ b/packages/schema/src/schemas/object.ts @@ -0,0 +1,305 @@ +/** + * t.object schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type * as T from '@traversable/registry' +import type { + Force, + Join, + Returns, + SchemaOptions as Options, + UnionToTuple, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + applyOptions, + Array_isArray, + bindUserExtensions, + fn, + has, + isAnyObject, + object as object$, + Object_assign, + Object_hasOwn, + Object_is, + Object_keys, + record as record$, + symbol, + typeName, + URI +} from '@traversable/registry' +import type { + Entry, + Optional, + Required, + Schema, + SchemaLike +} from '../_namespace.js' +import { getConfig, t } from '../_exports.js' +import type { RequiredKeys } from '@traversable/schema-to-json-schema' +import { isRequired, property } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { Errors, NullaryErrors, UnaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | T.Equal +export function equals(objectSchema: t.object): equals> +export function equals(objectSchema: t.object): equals> +export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { + function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + for (const k in def) { + const lHas = Object_hasOwn(l, k) + const rHas = Object_hasOwn(r, k) + if (lHas) { + if (!rHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (rHas) { + if (!lHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (!def[k].equals(l[k], r[k])) return false + } + return true + } + return objectEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema = RequiredKeys> { + (): { + type: 'object' + required: { [I in keyof KS]: KS[I] & string } + properties: { [K in keyof T]: Returns } + } +} + +export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { + const required = Object_keys(def).filter(isRequired(def)) + function objectToJsonSchema() { + return { + type: 'object' as const, + required, + properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), + } + } + return objectToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +/** @internal */ +type Symbol_optional = typeof Symbol_optional +const Symbol_optional: typeof symbol.optional = symbol.optional + +/** @internal */ +const hasOptionalSymbol = (u: unknown): u is { toString(): T } => + !!u && typeof u === 'function' + && Symbol_optional in u + && typeof u[Symbol_optional] === 'number' + +/** @internal */ +const hasToString = (x: unknown): x is { toString(): string } => + !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' + +export interface toString> { + (): never + | [keyof T] extends [never] ? '{}' + /* @ts-expect-error */ + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` +} + +export function toString>(objectSchema: t.object): toString +export function toString({ def }: t.object) { + function objectToString() { + if (!!def && typeof def === 'object') { + const entries = Object.entries(def) + if (entries.length === 0) return '{}' + else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" + }: ${hasToString(x) ? x.toString() : 'unknown' + }`).join(', ') + } }` + } + else return '{ [x: string]: unknown }' + } + + return objectToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +/** @internal */ +let isObject = (u: unknown): u is { [x: string]: unknown } => + !!u && typeof u === 'object' && !Array_isArray(u) + +/** @internal */ +let isKeyOf = (k: keyof any, u: T): k is keyof T => + !!u && (typeof u === 'function' || typeof u === 'object') && k in u + +/** @internal */ +let isOptional = has('tag', (tag) => tag === URI.optional) + + +export type validate = never | ValidationFn + +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { + validateObject.tag = URI.object + function validateObject(u: unknown, path_ = Array.of()) { + // if (objectSchema(u)) return true + if (!isObject(u)) return [Errors.object(u, path_)] + let errors = Array.of() + let { schema: { optionalTreatment } } = getConfig() + let keys = Object_keys(objectSchema.def) + if (optionalTreatment === 'exactOptional') { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (Object_hasOwn(u, k) && u[k] === undefined) { + if (isOptional(objectSchema.def[k].validate)) { + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] + errors.push(NullaryErrors[tag](...args)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + errors.push(NullaryErrors[tag](u[k], path, tag)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag].invalid(u[k], path)) + } + errors.push(...results) + } + else if (Object_hasOwn(u, k)) { + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + errors.push(...results) + continue + } else { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + } + } + else { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (!Object_hasOwn(u, k)) { + if (!isOptional(objectSchema.def[k].validate)) { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + else { + if (!Object_hasOwn(u, k)) continue + if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { + if (u[k] === undefined) continue + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let j = 0; j < results.length; j++) { + let result = results[j] + errors.push(result) + continue + } + } + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let l = 0; l < results.length; l++) { + let result = results[l] + errors.push(result) + } + } + } + return errors.length === 0 || errors + } + + return validateObject +} +/// validate /// +////////////////////// + +export { object_ as object } + +function object_< + S extends { [x: string]: Schema }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_< + S extends { [x: string]: SchemaLike }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_(schemas: { [x: string]: Schema }, options?: Options) { + return object_.def(schemas, options) +} + +interface object_ extends object_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +namespace object_ { + export let userDefinitions: Record = { + } as object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ + export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const keys = Object_keys(xs) + const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) + const req = keys.filter((k) => !has(symbol.optional)(xs[k])) + const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) + function ObjectSchema(src: unknown) { return predicate(src) } + ObjectSchema.tag = URI.object + ObjectSchema.def = xs + ObjectSchema.opt = opt + ObjectSchema.req = req + Object_assign(ObjectSchema, userDefinitions) + return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) + } +} + +declare namespace object_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: object_.type + tag: URI.object + get def(): S + opt: Optional + req: Required + } + type type< + S, + Opt extends Optional = Optional, + Req extends Required = Required, + T = Force< + & { [K in Req]-?: S[K]['_type' & keyof S[K]] } + & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } + > + > = never | T +} diff --git a/packages/schema/src/schemas/object/core.ts b/packages/schema/src/schemas/object/core.ts deleted file mode 100644 index 5b6116df..00000000 --- a/packages/schema/src/schemas/object/core.ts +++ /dev/null @@ -1,79 +0,0 @@ -import type { Force, SchemaOptions as Options, Unknown } from '@traversable/registry' -import { - applyOptions, - Array_isArray, - bindUserExtensions, - has, - _isPredicate, - Object_assign, - Object_keys, - record as record$, - object as object$, - isAnyObject, - symbol, - URI, -} from '@traversable/registry' - -import type { Entry, Optional, Required, Schema, SchemaLike } from '../../_namespace.js' - -export { object_ as object } - -function object_< - S extends { [x: string]: Schema }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_< - S extends { [x: string]: SchemaLike }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_(schemas: { [x: string]: Schema }, options?: Options) { - return object_.def(schemas, options) -} - -interface object_ extends object_.core { - //<%= Types %> -} - -namespace object_ { - export let userDefinitions: Record = { - //<%= Definitions %> - } as object_ - export function def(xs: T, $?: Options, opt?: string[]): object_ - /* v8 ignore next 1 */ - export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const keys = Object_keys(xs) - const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) - const req = keys.filter((k) => !has(symbol.optional)(xs[k])) - const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) - function ObjectSchema(src: unknown) { return predicate(src) } - ObjectSchema.tag = URI.object - ObjectSchema.def = xs - ObjectSchema.opt = opt - ObjectSchema.req = req - Object_assign(ObjectSchema, userDefinitions) - return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) - } -} - -declare namespace object_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: object_.type - tag: URI.object - get def(): S - opt: Optional - req: Required - } - type type< - S, - Opt extends Optional = Optional, - Req extends Required = Required, - T = Force< - & { [K in Req]-?: S[K]['_type' & keyof S[K]] } - & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } - > - > = never | T -} diff --git a/packages/schema/src/schemas/object/equals.ts b/packages/schema/src/schemas/object/equals.ts deleted file mode 100644 index ecc79d7a..00000000 --- a/packages/schema/src/schemas/object/equals.ts +++ /dev/null @@ -1,29 +0,0 @@ -import type * as T from '@traversable/registry' -import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' -import type { t } from '../../_exports.js' - -export type equals = never | T.Equal -export function equals(objectSchema: t.object): equals> -export function equals(objectSchema: t.object): equals> -export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { - function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { - if (Object_is(l, r)) return true - if (!l || typeof l !== 'object' || Array_isArray(l)) return false - if (!r || typeof r !== 'object' || Array_isArray(r)) return false - for (const k in def) { - const lHas = Object_hasOwn(l, k) - const rHas = Object_hasOwn(r, k) - if (lHas) { - if (!rHas) return false - if (!def[k].equals(l[k], r[k])) return false - } - if (rHas) { - if (!lHas) return false - if (!def[k].equals(l[k], r[k])) return false - } - if (!def[k].equals(l[k], r[k])) return false - } - return true - } - return objectEquals -} diff --git a/packages/schema/src/schemas/object/toJsonSchema.ts b/packages/schema/src/schemas/object/toJsonSchema.ts deleted file mode 100644 index 982b266a..00000000 --- a/packages/schema/src/schemas/object/toJsonSchema.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Returns } from '@traversable/registry' -import { fn, Object_keys } from '@traversable/registry' -import type { RequiredKeys } from '@traversable/schema-to-json-schema' -import { isRequired, property } from '@traversable/schema-to-json-schema' -import { t } from '../../_exports.js' - -export interface toJsonSchema = RequiredKeys> { - (): { - type: 'object' - required: { [I in keyof KS]: KS[I] & string } - properties: { [K in keyof T]: Returns } - } -} - -export function toJsonSchema(objectSchema: t.object): toJsonSchema -export function toJsonSchema(objectSchema: t.object): toJsonSchema -export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { - const required = Object_keys(def).filter(isRequired(def)) - function objectToJsonSchema() { - return { - type: 'object' as const, - required, - properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), - } - } - return objectToJsonSchema -} diff --git a/packages/schema/src/schemas/object/toString.ts b/packages/schema/src/schemas/object/toString.ts deleted file mode 100644 index f9a6968b..00000000 --- a/packages/schema/src/schemas/object/toString.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Join, UnionToTuple } from '@traversable/registry' -import { symbol } from '@traversable/registry' -import { t } from '../../_exports.js' - -/** @internal */ -type Symbol_optional = typeof Symbol_optional -const Symbol_optional: typeof symbol.optional = symbol.optional - -/** @internal */ -const hasOptionalSymbol = (u: unknown): u is { toString(): T } => - !!u && typeof u === 'function' - && Symbol_optional in u - && typeof u[Symbol_optional] === 'number' - -/** @internal */ -const hasToString = (x: unknown): x is { toString(): string } => - !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' - -export interface toString> { - (): never - | [keyof T] extends [never] ? '{}' - /* @ts-expect-error */ - : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` -} - -export function toString>(objectSchema: t.object): toString -export function toString({ def }: t.object) { - function objectToString() { - if (!!def && typeof def === 'object') { - const entries = Object.entries(def) - if (entries.length === 0) return '{}' - else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" - }: ${hasToString(x) ? x.toString() : 'unknown' - }`).join(', ') - } }` - } - else return '{ [x: string]: unknown }' - } - - return objectToString -} diff --git a/packages/schema/src/schemas/object/validate.ts b/packages/schema/src/schemas/object/validate.ts deleted file mode 100644 index 81cfe79d..00000000 --- a/packages/schema/src/schemas/object/validate.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - Array_isArray, - has, - Object_keys, - Object_hasOwn, - typeName, - URI, -} from '@traversable/registry' -import type { t } from '../../_exports.js' -import { getConfig } from '../../_exports.js' -import type { ValidationError, Validator, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors, Errors, UnaryErrors } from '@traversable/derive-validators' - -/** @internal */ -let isObject = (u: unknown): u is { [x: string]: unknown } => - !!u && typeof u === 'object' && !Array_isArray(u) - -/** @internal */ -let isKeyOf = (k: keyof any, u: T): k is keyof T => - !!u && (typeof u === 'function' || typeof u === 'object') && k in u - -/** @internal */ -let isOptional = has('tag', (tag) => tag === URI.optional) - - -export type validate = never | ValidationFn - -export function validate(objectSchema: t.object): validate -export function validate(objectSchema: t.object): validate -export function validate(objectSchema: t.object<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { - validateObject.tag = URI.object - function validateObject(u: unknown, path_ = Array.of()) { - // if (objectSchema(u)) return true - if (!isObject(u)) return [Errors.object(u, path_)] - let errors = Array.of() - let { schema: { optionalTreatment } } = getConfig() - let keys = Object_keys(objectSchema.def) - if (optionalTreatment === 'exactOptional') { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path = [...path_, k] - if (Object_hasOwn(u, k) && u[k] === undefined) { - if (isOptional(objectSchema.def[k].validate)) { - let tag = typeName(objectSchema.def[k].validate) - if (isKeyOf(tag, NullaryErrors)) { - let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] - errors.push(NullaryErrors[tag](...args)) - } - else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) - } - } - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - let tag = typeName(objectSchema.def[k].validate) - if (isKeyOf(tag, NullaryErrors)) { - errors.push(NullaryErrors[tag](u[k], path, tag)) - } - else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag].invalid(u[k], path)) - } - errors.push(...results) - } - else if (Object_hasOwn(u, k)) { - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - errors.push(...results) - continue - } else { - errors.push(UnaryErrors.object.missing(u, path)) - continue - } - } - } - else { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path = [...path_, k] - if (!Object_hasOwn(u, k)) { - if (!isOptional(objectSchema.def[k].validate)) { - errors.push(UnaryErrors.object.missing(u, path)) - continue - } - else { - if (!Object_hasOwn(u, k)) continue - if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { - if (u[k] === undefined) continue - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - for (let j = 0; j < results.length; j++) { - let result = results[j] - errors.push(result) - continue - } - } - } - } - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - for (let l = 0; l < results.length; l++) { - let result = results[l] - errors.push(result) - } - } - } - return errors.length === 0 || errors - } - - return validateObject -} diff --git a/packages/schema/src/schemas/of.ts b/packages/schema/src/schemas/of.ts new file mode 100644 index 00000000..b419a2ab --- /dev/null +++ b/packages/schema/src/schemas/of.ts @@ -0,0 +1,94 @@ +/** + * t.of schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Unknown } from '@traversable/registry' +import { Equal, Object_assign, URI } from '@traversable/registry' +import type { + Entry, + Guard, + Guarded, + SchemaLike +} from '../_namespace.js' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: T, right: T): boolean { + return Equal.lax(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function inlineToJsonSchema(): void { + return void 0 + } + return inlineToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(inlineSchema: t.of): validate { + validateInline.tag = URI.inline + function validateInline(u: unknown, path = Array.of()) { + return inlineSchema(u) || [NullaryErrors.inline(u, path)] + } + return validateInline +} +/// validate /// +////////////////////// + +export interface of extends of.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export function of(typeguard: S): Entry +export function of(typeguard: S): of +export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { + typeguard.def = typeguard + return Object_assign(typeguard, of.prototype) +} + +export namespace of { + export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, + } + export let userExtensions: Record = { + validate, + } + export function def(guard: T): of + export function def(guard: T) { + function InlineSchema(src: unknown) { return guard(src) } + InlineSchema.tag = URI.inline + InlineSchema.def = guard + return InlineSchema + } +} + +export declare namespace of { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: Guarded + tag: URI.inline + get def(): S + } + type type> = never | T +} diff --git a/packages/schema/src/schemas/of/core.ts b/packages/schema/src/schemas/of/core.ts deleted file mode 100644 index 48e7a300..00000000 --- a/packages/schema/src/schemas/of/core.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -import type { - Entry, - Guard, - Guarded, - SchemaLike, -} from '../../_namespace.js' - -export interface of extends of.core { - //<%= Types %> -} - -export function of(typeguard: S): Entry -export function of(typeguard: S): of -export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { - typeguard.def = typeguard - return Object_assign(typeguard, of.prototype) -} - -export namespace of { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(guard: T): of - /* v8 ignore next 6 */ - export function def(guard: T) { - function InlineSchema(src: unknown) { return guard(src) } - InlineSchema.tag = URI.inline - InlineSchema.def = guard - return InlineSchema - } -} - -export declare namespace of { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: Guarded - tag: URI.inline - get def(): S - } - type type> = never | T -} diff --git a/packages/schema/src/schemas/optional.ts b/packages/schema/src/schemas/optional.ts new file mode 100644 index 00000000..a6428edb --- /dev/null +++ b/packages/schema/src/schemas/optional.ts @@ -0,0 +1,135 @@ +/** + * t.optional schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Equal, + Force, + Returns, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + has, + isUnknown as isAny, + Object_assign, + Object_is, + optional as optional$, + symbol, + URI +} from '@traversable/registry' +import type { Entry, Schema, SchemaLike } from '../_namespace.js' +import { t } from '../_exports.js' +import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { Validate, ValidationFn, Validator } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal +export function equals(optionalSchema: t.optional): equals +export function equals(optionalSchema: t.optional): equals +export function equals({ def }: t.optional<{ equals: Equal }>): Equal { + return function optionalEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + return def.equals(l, r) + } +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +type Nullable = Force + +export interface toJsonSchema { + (): Nullable> + [symbol.optional]: number +} + +export function toJsonSchema(optionalSchema: t.optional): toJsonSchema +export function toJsonSchema({ def }: t.optional) { + function optionalToJsonSchema() { return getSchema(def) } + optionalToJsonSchema[symbol.optional] = wrapOptional(def) + return optionalToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType} | undefined)` +} + +export function toString(optionalSchema: t.optional): toString +export function toString({ def }: t.optional): () => string { + function optionalToString(): string { + return '(' + callToString(def) + ' | undefined)' + } + return optionalToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate + +export function validate(optionalSchema: t.optional): validate +export function validate(optionalSchema: t.optional): validate +export function validate({ def }: t.optional): ValidationFn { + validateOptional.tag = URI.optional + validateOptional.optional = 1 + function validateOptional(u: unknown, path = Array.of()) { + if (u === void 0) return true + return def.validate(u, path) + } + return validateOptional +} +/// validate /// +////////////////////// + +export function optional(schema: S): optional +export function optional(schema: S): optional> +export function optional(schema: S): optional { return optional.def(schema) } + +export interface optional extends optional.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace optional { + export let userDefinitions: Record = { + } + export function def(x: T): optional + export function def(x: T) { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const predicate = _isPredicate(x) ? optional$(x) : isAny + function OptionalSchema(src: unknown) { return predicate(src) } + OptionalSchema.tag = URI.optional + OptionalSchema.def = x + OptionalSchema[symbol.optional] = 1 + Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) + return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) + } + export const is + : (u: unknown) => u is optional + = has('tag', (u) => u === URI.optional) +} + +export declare namespace optional { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.optional + _type: undefined | S['_type' & keyof S] + def: S + [symbol.optional]: number + } + export type type = never | T +} diff --git a/packages/schema/src/schemas/optional/core.ts b/packages/schema/src/schemas/optional/core.ts deleted file mode 100644 index 77714964..00000000 --- a/packages/schema/src/schemas/optional/core.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - bindUserExtensions, - has, - _isPredicate, - optional as optional$, - Object_assign, - symbol, - URI, - isUnknown as isAny, -} from '@traversable/registry' - -import type { Entry, Schema, SchemaLike } from '../../_namespace.js' - -export function optional(schema: S): optional -export function optional(schema: S): optional> -export function optional(schema: S): optional { return optional.def(schema) } - -export interface optional extends optional.core { - //<%= Types %> -} - -export namespace optional { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(x: T): optional - export function def(x: T) { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = _isPredicate(x) ? optional$(x) : isAny - function OptionalSchema(src: unknown) { return predicate(src) } - OptionalSchema.tag = URI.optional - OptionalSchema.def = x - OptionalSchema[symbol.optional] = 1 - Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) - return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) - } - export const is - : (u: unknown) => u is optional - /* v8 ignore next 1 */ - = has('tag', (u) => u === URI.optional) -} - -export declare namespace optional { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.optional - _type: undefined | S['_type' & keyof S] - def: S - [symbol.optional]: number - } - export type type = never | T -} diff --git a/packages/schema/src/schemas/optional/equals.ts b/packages/schema/src/schemas/optional/equals.ts deleted file mode 100644 index 5ae67e4c..00000000 --- a/packages/schema/src/schemas/optional/equals.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' -import type { t } from '../../_exports.js' - -export type equals = never | Equal -export function equals(optionalSchema: t.optional): equals -export function equals(optionalSchema: t.optional): equals -export function equals({ def }: t.optional<{ equals: Equal }>): Equal { - return function optionalEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - return def.equals(l, r) - } -} diff --git a/packages/schema/src/schemas/optional/toJsonSchema.ts b/packages/schema/src/schemas/optional/toJsonSchema.ts deleted file mode 100644 index 922ec2b8..00000000 --- a/packages/schema/src/schemas/optional/toJsonSchema.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Force } from '@traversable/registry' -import type { Returns } from '@traversable/registry' -import { symbol } from '@traversable/registry' -import type { t } from '../../_exports.js' -import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' - -type Nullable = Force - -export interface toJsonSchema { - (): Nullable> - [symbol.optional]: number -} - -export function toJsonSchema(optionalSchema: t.optional): toJsonSchema -export function toJsonSchema({ def }: t.optional) { - function optionalToJsonSchema() { return getSchema(def) } - optionalToJsonSchema[symbol.optional] = wrapOptional(def) - return optionalToJsonSchema -} diff --git a/packages/schema/src/schemas/optional/toString.ts b/packages/schema/src/schemas/optional/toString.ts deleted file mode 100644 index f5d02852..00000000 --- a/packages/schema/src/schemas/optional/toString.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { t } from '../../_exports.js' -import { callToString } from '@traversable/schema-to-string' - -export interface toString { - /* @ts-expect-error */ - (): never | `(${ReturnType} | undefined)` -} - -export function toString(optionalSchema: t.optional): toString -export function toString({ def }: t.optional): () => string { - function optionalToString(): string { - return '(' + callToString(def) + ' | undefined)' - } - return optionalToString -} diff --git a/packages/schema/src/schemas/optional/validate.ts b/packages/schema/src/schemas/optional/validate.ts deleted file mode 100644 index bf4f366e..00000000 --- a/packages/schema/src/schemas/optional/validate.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { URI } from '@traversable/registry' -import { t } from '../../_exports.js' -import type { Validate, Validator, ValidationFn } from '@traversable/derive-validators' - -export type validate = Validate - -export function validate(optionalSchema: t.optional): validate -export function validate(optionalSchema: t.optional): validate -export function validate({ def }: t.optional): ValidationFn { - validateOptional.tag = URI.optional - validateOptional.optional = 1 - function validateOptional(u: unknown, path = Array.of()) { - if (u === void 0) return true - return def.validate(u, path) - } - return validateOptional -} diff --git a/packages/schema/src/schemas/record.ts b/packages/schema/src/schemas/record.ts new file mode 100644 index 00000000..c0b7121e --- /dev/null +++ b/packages/schema/src/schemas/record.ts @@ -0,0 +1,160 @@ +/** + * t.record schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type * as T from '@traversable/registry' +import type { Equal, Returns, Unknown } from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + isAnyObject, + Object_assign, + Object_hasOwn, + Object_is, + Object_keys, + record as record$, + URI +} from '@traversable/registry' +import type { Entry, Schema, SchemaLike } from '../_namespace.js' +import type { t } from '../_exports.js' +import { getSchema } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal +export function equals(recordSchema: t.record): equals +export function equals(recordSchema: t.record): equals +export function equals({ def }: t.record<{ equals: Equal }>): Equal> { + function recordEquals(l: Record, r: Record): boolean { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + const lhs = Object_keys(l) + const rhs = Object_keys(r) + let len = lhs.length + let k: string + if (len !== rhs.length) return false + for (let ix = len; ix-- !== 0;) { + k = lhs[ix] + if (!Object_hasOwn(r, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + len = rhs.length + for (let ix = len; ix-- !== 0;) { + k = rhs[ix] + if (!Object_hasOwn(l, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + return true + } + return recordEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { + type: 'object' + additionalProperties: T.Returns + } +} + +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { + return function recordToJsonSchema() { + return { + type: 'object' as const, + additionalProperties: getSchema(def), + } + } +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + /* @ts-expect-error */ + (): never | `Record}>` +} + +export function toString>(recordSchema: S): toString +export function toString(recordSchema: t.record): toString +export function toString({ def }: { def: unknown }): () => string { + function recordToString() { + return `Record` + } + return recordToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = never | ValidationFn +export function validate(recordSchema: t.record): validate +export function validate(recordSchema: t.record): validate +export function validate({ def: { validate = () => true } }: t.record) { + validateRecord.tag = URI.record + function validateRecord(u: unknown, path = Array.of()) { + if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] + let errors = Array.of() + let keys = Object_keys(u) + for (let k of keys) { + let y = u[k] + let results = validate(y, [...path, k]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateRecord +} +/// validate /// +////////////////////// + +export function record(schema: S): record +export function record(schema: S): record> +export function record(schema: Schema) { + return record.def(schema) +} + +export interface record extends record.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace record { + export let userDefinitions: Record = { + } + export function def(x: T): record + export function def(x: unknown): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const predicate = _isPredicate(x) ? record$(x) : isAnyObject + function RecordSchema(src: unknown) { return predicate(src) } + RecordSchema.tag = URI.record + RecordSchema.def = x + Object_assign(RecordSchema, record.userDefinitions) + return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) + } +} + +export declare namespace record { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.record + get def(): S + _type: Record + } + export type type> = never | T +} diff --git a/packages/schema/src/schemas/record/core.ts b/packages/schema/src/schemas/record/core.ts deleted file mode 100644 index 55819b72..00000000 --- a/packages/schema/src/schemas/record/core.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - isAnyObject, - record as record$, - bindUserExtensions, - _isPredicate, - Object_assign, - URI, -} from '@traversable/registry' - -import type { Entry, Schema, SchemaLike } from '../../_namespace.js' - -export function record(schema: S): record -export function record(schema: S): record> -export function record(schema: Schema) { - return record.def(schema) -} - -export interface record extends record.core { - //<%= Types %> -} - -export namespace record { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(x: T): record - /* v8 ignore next 1 */ - export function def(x: unknown): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = _isPredicate(x) ? record$(x) : isAnyObject - function RecordSchema(src: unknown) { return predicate(src) } - RecordSchema.tag = URI.record - RecordSchema.def = x - Object_assign(RecordSchema, record.userDefinitions) - return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) - } -} - -export declare namespace record { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.record - get def(): S - _type: Record - } - export type type> = never | T -} diff --git a/packages/schema/src/schemas/record/equals.ts b/packages/schema/src/schemas/record/equals.ts deleted file mode 100644 index ecbf81d7..00000000 --- a/packages/schema/src/schemas/record/equals.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Array_isArray, Object_is, Object_keys, Object_hasOwn } from '@traversable/registry' -import type { t } from '../../_exports.js' - -export type equals = never | Equal -export function equals(recordSchema: t.record): equals -export function equals(recordSchema: t.record): equals -export function equals({ def }: t.record<{ equals: Equal }>): Equal> { - function recordEquals(l: Record, r: Record): boolean { - if (Object_is(l, r)) return true - if (!l || typeof l !== 'object' || Array_isArray(l)) return false - if (!r || typeof r !== 'object' || Array_isArray(r)) return false - const lhs = Object_keys(l) - const rhs = Object_keys(r) - let len = lhs.length - let k: string - if (len !== rhs.length) return false - for (let ix = len; ix-- !== 0;) { - k = lhs[ix] - if (!Object_hasOwn(r, k)) return false - if (!(def.equals(l[k], r[k]))) return false - } - len = rhs.length - for (let ix = len; ix-- !== 0;) { - k = rhs[ix] - if (!Object_hasOwn(l, k)) return false - if (!(def.equals(l[k], r[k]))) return false - } - return true - } - return recordEquals -} diff --git a/packages/schema/src/schemas/record/toJsonSchema.ts b/packages/schema/src/schemas/record/toJsonSchema.ts deleted file mode 100644 index f01343fe..00000000 --- a/packages/schema/src/schemas/record/toJsonSchema.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { t } from '../../_exports.js' -import type * as T from '@traversable/registry' -import { getSchema } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): { - type: 'object' - additionalProperties: T.Returns - } -} - -export function toJsonSchema(recordSchema: t.record): toJsonSchema -export function toJsonSchema(recordSchema: t.record): toJsonSchema -export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { - return function recordToJsonSchema() { - return { - type: 'object' as const, - additionalProperties: getSchema(def), - } - } -} diff --git a/packages/schema/src/schemas/record/toString.ts b/packages/schema/src/schemas/record/toString.ts deleted file mode 100644 index 1ef8dd01..00000000 --- a/packages/schema/src/schemas/record/toString.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Returns } from '@traversable/registry' -import type { t } from '../../_exports.js' -import { callToString } from '@traversable/schema-to-string' - -export interface toString { - /* @ts-expect-error */ - (): never | `Record}>` -} - -export function toString>(recordSchema: S): toString -export function toString(recordSchema: t.record): toString -export function toString({ def }: { def: unknown }): () => string { - function recordToString() { - return `Record` - } - return recordToString -} diff --git a/packages/schema/src/schemas/record/validate.ts b/packages/schema/src/schemas/record/validate.ts deleted file mode 100644 index 10703a7f..00000000 --- a/packages/schema/src/schemas/record/validate.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { t } from '../../_exports.js' -import { Array_isArray, Object_keys, URI } from '@traversable/registry' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = never | ValidationFn -export function validate(recordSchema: t.record): validate -export function validate(recordSchema: t.record): validate -export function validate({ def: { validate = () => true } }: t.record) { - validateRecord.tag = URI.record - function validateRecord(u: unknown, path = Array.of()) { - if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] - let errors = Array.of() - let keys = Object_keys(u) - for (let k of keys) { - let y = u[k] - let results = validate(y, [...path, k]) - if (results === true) continue - else errors.push(...results) - } - return errors.length === 0 || errors - } - return validateRecord -} diff --git a/packages/schema/src/schemas/string/core.ts b/packages/schema/src/schemas/string.ts similarity index 54% rename from packages/schema/src/schemas/string/core.ts rename to packages/schema/src/schemas/string.ts index 4276ca17..4d795695 100644 --- a/packages/schema/src/schemas/string/core.ts +++ b/packages/schema/src/schemas/string.ts @@ -1,14 +1,77 @@ -import type { Bounds, Integer, Unknown } from '@traversable/registry' +/** + * t.string schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Bounds, + Equal, + Force, + Integer, + PickIfDefined, + Unknown +} from '@traversable/registry' import { bindUserExtensions, carryover, - Math_min, + has, Math_max, + Math_min, Object_assign, URI, - within, + within } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { SizeBounds } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: string, right: string): boolean { + return left === right +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): Force<{ type: 'string' } & PickIfDefined> +} +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.string): () => { type: 'string' } & Partial { + function stringToJsonSchema() { + const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null + const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null + let out: { type: 'string' } & Partial = { type: 'string' } + minLength !== null && void (out.minLength = minLength) + maxLength !== null && void (out.maxLength = maxLength) + + return out + } + return stringToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'string' } +export function toString(): 'string' { return 'string' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(stringSchema: S): validate { + validateString.tag = URI.string + function validateString(u: unknown, path = Array.of()): true | ValidationError[] { + return stringSchema(u) || [NullaryErrors.number(u, path)] + } + return validateString +} +/// validate /// +////////////////////// + export { string_ as string } /** @internal */ @@ -21,15 +84,20 @@ function boundedString(bounds: Bounds, carry?: {}): {} { } interface string_ extends string_.core { - //<%= Types %> + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate } export let userDefinitions: Record = { - //<%= Definitions %> + toString, + equals, } export let userExtensions: Record = { - //<%= Extensions %> + toJsonSchema, + validate, } function StringSchema(src: unknown) { return typeof src === 'string' } diff --git a/packages/schema/src/schemas/string/equals.ts b/packages/schema/src/schemas/string/equals.ts deleted file mode 100644 index b9444108..00000000 --- a/packages/schema/src/schemas/string/equals.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Equal } from '@traversable/registry' - -export type equals = Equal -export function equals(left: string, right: string): boolean { - return left === right -} diff --git a/packages/schema/src/schemas/string/toJsonSchema.ts b/packages/schema/src/schemas/string/toJsonSchema.ts deleted file mode 100644 index 245236f5..00000000 --- a/packages/schema/src/schemas/string/toJsonSchema.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Force, PickIfDefined } from '@traversable/registry' -import type { t } from '../../_exports.js' -import { has } from '@traversable/registry' -import type { SizeBounds } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): Force<{ type: 'string' } & PickIfDefined> -} - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: t.string): () => { type: 'string' } & Partial { - function stringToJsonSchema() { - const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null - const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null - let out: { type: 'string' } & Partial = { type: 'string' } - minLength !== null && void (out.minLength = minLength) - maxLength !== null && void (out.maxLength = maxLength) - - return out - } - return stringToJsonSchema -} diff --git a/packages/schema/src/schemas/string/toString.ts b/packages/schema/src/schemas/string/toString.ts deleted file mode 100644 index 86a98e16..00000000 --- a/packages/schema/src/schemas/string/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'string' } -export function toString(): 'string' { return 'string' } diff --git a/packages/schema/src/schemas/string/validate.ts b/packages/schema/src/schemas/string/validate.ts deleted file mode 100644 index 22937e80..00000000 --- a/packages/schema/src/schemas/string/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(stringSchema: S): validate { - validateString.tag = URI.string - function validateString(u: unknown, path = Array.of()): true | ValidationError[] { - return stringSchema(u) || [NullaryErrors.number(u, path)] - } - return validateString -} diff --git a/packages/schema/src/schemas/symbol.ts b/packages/schema/src/schemas/symbol.ts new file mode 100644 index 00000000..199ba57b --- /dev/null +++ b/packages/schema/src/schemas/symbol.ts @@ -0,0 +1,83 @@ +/** + * t.symbol schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: symbol, right: symbol): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function symbolToJsonSchema() { return void 0 } + return symbolToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'symbol' } +export function toString(): 'symbol' { return 'symbol' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(symbolSchema: t.symbol): validate { + validateSymbol.tag = URI.symbol + function validateSymbol(u: unknown, path = Array.of()) { + return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] + } + return validateSymbol +} +/// validate /// +////////////////////// + +export { symbol_ as symbol } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface symbol_ extends symbol_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } +SymbolSchema.tag = URI.symbol +SymbolSchema.def = Symbol() + +const symbol_ = Object_assign( + SymbolSchema, + userDefinitions, +) as symbol_ + +Object_assign(symbol_, userExtensions) + +declare namespace symbol_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.symbol + _type: symbol + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/symbol/core.ts b/packages/schema/src/schemas/symbol/core.ts deleted file mode 100644 index 6e192b3e..00000000 --- a/packages/schema/src/schemas/symbol/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { symbol_ as symbol } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface symbol_ extends symbol_.core { - //<%= Types %> -} - -function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } -SymbolSchema.tag = URI.symbol -SymbolSchema.def = Symbol() - -const symbol_ = Object_assign( - SymbolSchema, - userDefinitions, -) as symbol_ - -Object_assign(symbol_, userExtensions) - -declare namespace symbol_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.symbol - _type: symbol - get def(): this['_type'] - } -} diff --git a/packages/schema/src/schemas/symbol/equals.ts b/packages/schema/src/schemas/symbol/equals.ts deleted file mode 100644 index f3bb7486..00000000 --- a/packages/schema/src/schemas/symbol/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: symbol, right: symbol): boolean { - return Object_is(left, right) -} diff --git a/packages/schema/src/schemas/symbol/toJsonSchema.ts b/packages/schema/src/schemas/symbol/toJsonSchema.ts deleted file mode 100644 index 7046b08e..00000000 --- a/packages/schema/src/schemas/symbol/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function symbolToJsonSchema() { return void 0 } - return symbolToJsonSchema -} diff --git a/packages/schema/src/schemas/symbol/toString.ts b/packages/schema/src/schemas/symbol/toString.ts deleted file mode 100644 index 5651fe27..00000000 --- a/packages/schema/src/schemas/symbol/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'symbol' } -export function toString(): 'symbol' { return 'symbol' } diff --git a/packages/schema/src/schemas/symbol/validate.ts b/packages/schema/src/schemas/symbol/validate.ts deleted file mode 100644 index e0c26452..00000000 --- a/packages/schema/src/schemas/symbol/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(symbolSchema: t.symbol): validate { - validateSymbol.tag = URI.symbol - function validateSymbol(u: unknown, path = Array.of()) { - return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] - } - return validateSymbol -} diff --git a/packages/schema/src/schemas/tuple.ts b/packages/schema/src/schemas/tuple.ts new file mode 100644 index 00000000..207b3e0e --- /dev/null +++ b/packages/schema/src/schemas/tuple.ts @@ -0,0 +1,223 @@ +/** + * t.tuple schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Equal, + Join, + Returns, + SchemaOptions as Options, + TypeError, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + getConfig, + has, + Object_assign, + Object_hasOwn, + Object_is, + parseArgs, + symbol, + tuple as tuple$, + URI +} from '@traversable/registry' +import type { + Entry, + FirstOptionalItem, + invalid, + Schema, + SchemaLike, + TupleType, + ValidateTuple +} from '../_namespace.js' +import type { optional } from './optional.js' +import { t } from '../_exports.js' +import type { MinItems } from '@traversable/schema-to-json-schema' +import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' +import { hasToString } from '@traversable/schema-to-string' +import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal + +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple) { + function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + for (let ix = tupleSchema.def.length; ix-- !== 0;) { + if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue + if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false + if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false + if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { + if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false + } + } + return true + } + return false + } + return tupleEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { + type: 'array', + items: { [I in keyof T]: Returns } + additionalItems: false + minItems: MinItems + maxItems: T['length' & keyof T] + } +} + +export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema +export function toJsonSchema({ def }: t.tuple): () => { + type: 'array' + items: unknown + additionalItems: false + minItems?: {} + maxItems?: number +} { + function tupleToJsonSchema() { + let min = minItems(def) + let max = def.length + let items = applyTupleOptionality(def, { min, max }) + return { + type: 'array' as const, + additionalItems: false as const, + items, + minItems: min, + maxItems: max, + } + } + return tupleToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): never | `[${Join<{ + [I in keyof T]: `${ + /* @ts-expect-error */ + T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType + }` + }, ', '>}]` +} + +export function toString(tupleSchema: t.tuple): toString +export function toString(tupleSchema: t.tuple): () => string { + function stringToString() { + return Array_isArray(tupleSchema.def) + ? `[${tupleSchema.def.map( + (x) => t.optional.is(x) + ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` + : hasToString(x) ? x.toString() : 'unknown' + ).join(', ')}]` : 'unknown[]' + } + return stringToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): Validate { + validateTuple.tag = URI.tuple + function validateTuple(u: unknown, path = Array.of()) { + let errors = Array.of() + if (!Array_isArray(u)) return [Errors.array(u, path)] + for (let i = 0; i < tupleSchema.def.length; i++) { + if (!(i in u) && !(t.optional.is(tupleSchema.def[i].validate))) { + errors.push(Errors.missingIndex(u, [...path, i])) + continue + } + let results = tupleSchema.def[i].validate(u[i], [...path, i]) + if (results !== true) { + for (let j = 0; j < results.length; j++) errors.push(results[j]) + results.push(Errors.arrayElement(u[i], [...path, i])) + } + } + if (u.length > tupleSchema.def.length) { + for (let k = tupleSchema.def.length; k < u.length; k++) { + let excess = u[k] + errors.push(Errors.excessItems(excess, [...path, k])) + } + } + return errors.length === 0 || errors + } + return validateTuple +} +/// validate /// +////////////////////// + +export { tuple } + +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { + return tuple.def(...parseArgs(getConfig().schema, args)) +} + +interface tuple extends tuple.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +namespace tuple { + export let userDefinitions: Record = { + } as tuple + export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple + export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const opt = opt_ || xs.findIndex(has(symbol.optional)) + const options = { + ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) + } satisfies tuple.InternalOptions + const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) + function TupleSchema(src: unknown) { return predicate(src) } + TupleSchema.tag = URI.tuple + TupleSchema.def = xs + TupleSchema.opt = opt + Object_assign(TupleSchema, tuple.userDefinitions) + return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) + } +} + +declare namespace tuple { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.tuple + _type: TupleType + opt: FirstOptionalItem + def: S + } + type type> = never | T + type InternalOptions = { minLength?: number } + type validate = ValidateTuple> + + type from + = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T +} diff --git a/packages/schema/src/schemas/tuple/core.ts b/packages/schema/src/schemas/tuple/core.ts deleted file mode 100644 index ce6e91cb..00000000 --- a/packages/schema/src/schemas/tuple/core.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { - SchemaOptions as Options, - TypeError, - Unknown -} from '@traversable/registry' - -import { - Array_isArray, - bindUserExtensions, - getConfig, - has, - _isPredicate, - Object_assign, - parseArgs, - symbol, - tuple as tuple$, - URI, -} from '@traversable/registry' - -import type { - Entry, - FirstOptionalItem, - invalid, - Schema, - SchemaLike, - TupleType, - ValidateTuple -} from '../../_namespace.js' - -import type { optional } from '../optional/core.ts' - -export { tuple } - -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> -function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { - return tuple.def(...parseArgs(getConfig().schema, args)) -} - -interface tuple extends tuple.core { - //<%= Types %> -} - -namespace tuple { - export let userDefinitions: Record = { - //<%= Definitions %> - } as tuple - export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple - /* v8 ignore next 1 */ - export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const opt = opt_ || xs.findIndex(has(symbol.optional)) - const options = { - ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) - } satisfies tuple.InternalOptions - const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) - function TupleSchema(src: unknown) { return predicate(src) } - TupleSchema.tag = URI.tuple - TupleSchema.def = xs - TupleSchema.opt = opt - Object_assign(TupleSchema, tuple.userDefinitions) - return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) - } -} - -declare namespace tuple { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.tuple - _type: TupleType - opt: FirstOptionalItem - def: S - } - type type> = never | T - type InternalOptions = { minLength?: number } - type validate = ValidateTuple> - - type from - = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T -} diff --git a/packages/schema/src/schemas/tuple/equals.ts b/packages/schema/src/schemas/tuple/equals.ts deleted file mode 100644 index 54ea9a02..00000000 --- a/packages/schema/src/schemas/tuple/equals.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' -import { t } from '../../_exports.js' - -export type equals = Equal - -export function equals(tupleSchema: t.tuple): equals -export function equals(tupleSchema: t.tuple): equals -export function equals(tupleSchema: t.tuple) { - function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { - if (Object_is(l, r)) return true - if (Array_isArray(l)) { - if (!Array_isArray(r)) return false - for (let ix = tupleSchema.def.length; ix-- !== 0;) { - if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue - if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false - if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false - if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { - if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false - } - } - return true - } - return false - } - return tupleEquals -} diff --git a/packages/schema/src/schemas/tuple/toJsonSchema.ts b/packages/schema/src/schemas/tuple/toJsonSchema.ts deleted file mode 100644 index 387c9d05..00000000 --- a/packages/schema/src/schemas/tuple/toJsonSchema.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Returns } from '@traversable/registry' -import { t } from '../../_exports.js' -import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' -import type { MinItems } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): { - type: 'array', - items: { [I in keyof T]: Returns } - additionalItems: false - minItems: MinItems - maxItems: T['length' & keyof T] - } -} - -export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema -export function toJsonSchema({ def }: t.tuple): () => { - type: 'array' - items: unknown - additionalItems: false - minItems?: {} - maxItems?: number -} { - function tupleToJsonSchema() { - let min = minItems(def) - let max = def.length - let items = applyTupleOptionality(def, { min, max }) - return { - type: 'array' as const, - additionalItems: false as const, - items, - minItems: min, - maxItems: max, - } - } - return tupleToJsonSchema -} diff --git a/packages/schema/src/schemas/tuple/toString.ts b/packages/schema/src/schemas/tuple/toString.ts deleted file mode 100644 index 9e6d1eaf..00000000 --- a/packages/schema/src/schemas/tuple/toString.ts +++ /dev/null @@ -1,26 +0,0 @@ -import type { Join } from '@traversable/registry' -import { Array_isArray } from '@traversable/registry' -import { t } from '../../_exports.js' -import { hasToString } from '@traversable/schema-to-string' - -export interface toString { - (): never | `[${Join<{ - [I in keyof T]: `${ - /* @ts-expect-error */ - T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType - }` - }, ', '>}]` -} - -export function toString(tupleSchema: t.tuple): toString -export function toString(tupleSchema: t.tuple): () => string { - function stringToString() { - return Array_isArray(tupleSchema.def) - ? `[${tupleSchema.def.map( - (x) => t.optional.is(x) - ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` - : hasToString(x) ? x.toString() : 'unknown' - ).join(', ')}]` : 'unknown[]' - } - return stringToString -} diff --git a/packages/schema/src/schemas/tuple/validate.ts b/packages/schema/src/schemas/tuple/validate.ts deleted file mode 100644 index 672f66e3..00000000 --- a/packages/schema/src/schemas/tuple/validate.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { URI, Array_isArray } from '@traversable/registry' -import { t } from '../../_exports.js' -import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' -import { Errors } from '@traversable/derive-validators' - -export type validate = Validate -export function validate(tupleSchema: t.tuple<[...S]>): validate -export function validate(tupleSchema: t.tuple<[...S]>): validate -export function validate(tupleSchema: t.tuple<[...S]>): Validate { - validateTuple.tag = URI.tuple - function validateTuple(u: unknown, path = Array.of()) { - let errors = Array.of() - if (!Array_isArray(u)) return [Errors.array(u, path)] - for (let i = 0; i < tupleSchema.def.length; i++) { - if (!(i in u) && !(t.optional.is(tupleSchema.def[i].validate))) { - errors.push(Errors.missingIndex(u, [...path, i])) - continue - } - let results = tupleSchema.def[i].validate(u[i], [...path, i]) - if (results !== true) { - for (let j = 0; j < results.length; j++) errors.push(results[j]) - results.push(Errors.arrayElement(u[i], [...path, i])) - } - } - if (u.length > tupleSchema.def.length) { - for (let k = tupleSchema.def.length; k < u.length; k++) { - let excess = u[k] - errors.push(Errors.excessItems(excess, [...path, k])) - } - } - return errors.length === 0 || errors - } - return validateTuple -} diff --git a/packages/schema/src/schemas/undefined.ts b/packages/schema/src/schemas/undefined.ts new file mode 100644 index 00000000..8a3034e3 --- /dev/null +++ b/packages/schema/src/schemas/undefined.ts @@ -0,0 +1,83 @@ +/** + * t.undefined schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: undefined, right: undefined): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function undefinedToJsonSchema(): void { return void 0 } + return undefinedToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'undefined' } +export function toString(): 'undefined' { return 'undefined' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(undefinedSchema: t.undefined): validate { + validateUndefined.tag = URI.undefined + function validateUndefined(u: unknown, path = Array.of()) { + return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] + } + return validateUndefined +} +/// validate /// +////////////////////// + +export { undefined_ as undefined } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface undefined_ extends undefined_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } +UndefinedSchema.tag = URI.undefined +UndefinedSchema.def = void 0 as undefined + +const undefined_ = Object_assign( + UndefinedSchema, + userDefinitions, +) as undefined_ + +Object_assign(undefined_, userExtensions) + +declare namespace undefined_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.undefined + _type: undefined + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/undefined/core.ts b/packages/schema/src/schemas/undefined/core.ts deleted file mode 100644 index 0115f168..00000000 --- a/packages/schema/src/schemas/undefined/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { undefined_ as undefined } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface undefined_ extends undefined_.core { - //<%= Types %> -} - -function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } -UndefinedSchema.tag = URI.undefined -UndefinedSchema.def = void 0 as undefined - -const undefined_ = Object_assign( - UndefinedSchema, - userDefinitions, -) as undefined_ - -Object_assign(undefined_, userExtensions) - -declare namespace undefined_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.undefined - _type: undefined - get def(): this['_type'] - } -} diff --git a/packages/schema/src/schemas/undefined/equals.ts b/packages/schema/src/schemas/undefined/equals.ts deleted file mode 100644 index 75156d56..00000000 --- a/packages/schema/src/schemas/undefined/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: undefined, right: undefined): boolean { - return Object_is(left, right) -} diff --git a/packages/schema/src/schemas/undefined/toJsonSchema.ts b/packages/schema/src/schemas/undefined/toJsonSchema.ts deleted file mode 100644 index be46c306..00000000 --- a/packages/schema/src/schemas/undefined/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function undefinedToJsonSchema(): void { return void 0 } - return undefinedToJsonSchema -} diff --git a/packages/schema/src/schemas/undefined/toString.ts b/packages/schema/src/schemas/undefined/toString.ts deleted file mode 100644 index a48b744b..00000000 --- a/packages/schema/src/schemas/undefined/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'undefined' } -export function toString(): 'undefined' { return 'undefined' } diff --git a/packages/schema/src/schemas/undefined/validate.ts b/packages/schema/src/schemas/undefined/validate.ts deleted file mode 100644 index 8e0baa0a..00000000 --- a/packages/schema/src/schemas/undefined/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(undefinedSchema: t.undefined): validate { - validateUndefined.tag = URI.undefined - function validateUndefined(u: unknown, path = Array.of()) { - return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] - } - return validateUndefined -} diff --git a/packages/schema/src/schemas/union.ts b/packages/schema/src/schemas/union.ts new file mode 100644 index 00000000..85c1fbdc --- /dev/null +++ b/packages/schema/src/schemas/union.ts @@ -0,0 +1,144 @@ +/** + * t.union schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Equal, + Join, + Returns, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + isUnknown as isAny, + Object_assign, + Object_is, + union as union$, + URI +} from '@traversable/registry' +import type { Entry, Schema, SchemaLike } from '../_namespace.js' +import { t } from '../_exports.js' +import { getSchema } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(unionSchema: t.union<[...S]>): equals +export function equals(unionSchema: t.union<[...S]>): equals +export function equals({ def }: t.union<{ equals: Equal }[]>): Equal { + function unionEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (def[ix].equals(l, r)) return true + return false + } + return unionEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { anyOf: { [I in keyof T]: Returns } } +} + +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema({ def }: t.union): () => {} { + return function unionToJsonSchema() { + return { + anyOf: def.map(getSchema) + } + } +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` +} + +export function toString(unionSchema: t.union): toString +export function toString({ def }: t.union): () => string { + function unionToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' + } + return unionToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate + +export function validate(unionSchema: t.union): validate +export function validate(unionSchema: t.union): validate +export function validate({ def }: t.union) { + validateUnion.tag = URI.union + function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { + // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results === true) { + // validateUnion.optional = 0 + return true + } + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + // validateUnion.optional = 0 + return errors.length === 0 || errors + } + return validateUnion +} +/// validate /// +////////////////////// + +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: unknown[]) { + return union.def(schemas) +} + +export interface union extends union.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace union { + export let userDefinitions: Record = { + } as Partial> + export function def(xs: T): union + export function def(xs: unknown[]) { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const predicate = xs.every(_isPredicate) ? union$(xs) : isAny + function UnionSchema(src: unknown): src is unknown { return predicate(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.userDefinitions) + return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) + } +} + +export declare namespace union { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.union + _type: union.type + get def(): S + } + type type = never | T +} diff --git a/packages/schema/src/schemas/union/core.ts b/packages/schema/src/schemas/union/core.ts deleted file mode 100644 index 5fee08b4..00000000 --- a/packages/schema/src/schemas/union/core.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - _isPredicate, - bindUserExtensions, - isUnknown as isAny, - Object_assign, - union as union$, - URI, -} from '@traversable/registry' - -import type { Entry, Schema, SchemaLike } from '../../_namespace.js' - -export function union(...schemas: S): union -export function union }>(...schemas: S): union -export function union(...schemas: unknown[]) { - return union.def(schemas) -} - -export interface union extends union.core { - //<%= Types %> -} - -export namespace union { - export let userDefinitions: Record = { - //<%= Definitions %> - } as Partial> - export function def(xs: T): union - /* v8 ignore next 1 */ - export function def(xs: unknown[]) { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = xs.every(_isPredicate) ? union$(xs) : isAny - function UnionSchema(src: unknown): src is unknown { return predicate(src) } - UnionSchema.tag = URI.union - UnionSchema.def = xs - Object_assign(UnionSchema, union.userDefinitions) - return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) - } -} - -export declare namespace union { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.union - _type: union.type - get def(): S - } - type type = never | T -} diff --git a/packages/schema/src/schemas/union/equals.ts b/packages/schema/src/schemas/union/equals.ts deleted file mode 100644 index 59900d7f..00000000 --- a/packages/schema/src/schemas/union/equals.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' -import type { t } from '../../_exports.js' - -export type equals = Equal -export function equals(unionSchema: t.union<[...S]>): equals -export function equals(unionSchema: t.union<[...S]>): equals -export function equals({ def }: t.union<{ equals: Equal }[]>): Equal { - function unionEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - for (let ix = def.length; ix-- !== 0;) - if (def[ix].equals(l, r)) return true - return false - } - return unionEquals -} diff --git a/packages/schema/src/schemas/union/toJsonSchema.ts b/packages/schema/src/schemas/union/toJsonSchema.ts deleted file mode 100644 index a8ec85b9..00000000 --- a/packages/schema/src/schemas/union/toJsonSchema.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Returns } from '@traversable/registry' -import { t } from '../../_exports.js' -import { getSchema } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): { anyOf: { [I in keyof T]: Returns } } -} - -export function toJsonSchema(unionSchema: t.union): toJsonSchema -export function toJsonSchema(unionSchema: t.union): toJsonSchema -export function toJsonSchema({ def }: t.union): () => {} { - return function unionToJsonSchema() { - return { - anyOf: def.map(getSchema) - } - } -} diff --git a/packages/schema/src/schemas/union/toString.ts b/packages/schema/src/schemas/union/toString.ts deleted file mode 100644 index 2999cb50..00000000 --- a/packages/schema/src/schemas/union/toString.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Join } from '@traversable/registry' -import { Array_isArray } from '@traversable/registry' -import { t } from '../../_exports.js' -import { callToString } from '@traversable/schema-to-string' - -export interface toString { - (): never | [T] extends [readonly []] ? 'never' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` -} - -export function toString(unionSchema: t.union): toString -export function toString({ def }: t.union): () => string { - function unionToString() { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' - } - return unionToString -} diff --git a/packages/schema/src/schemas/union/validate.ts b/packages/schema/src/schemas/union/validate.ts deleted file mode 100644 index a1658df2..00000000 --- a/packages/schema/src/schemas/union/validate.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { URI } from '@traversable/registry' -import { t } from '../../_exports.js' -import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' - -export type validate = Validate - -export function validate(unionSchema: t.union): validate -export function validate(unionSchema: t.union): validate -export function validate({ def }: t.union) { - validateUnion.tag = URI.union - function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { - // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; - let errors = Array.of() - for (let i = 0; i < def.length; i++) { - let results = def[i].validate(u, path) - if (results === true) { - // validateUnion.optional = 0 - return true - } - for (let j = 0; j < results.length; j++) errors.push(results[j]) - } - // validateUnion.optional = 0 - return errors.length === 0 || errors - } - return validateUnion -} diff --git a/packages/schema/src/schemas/unknown.ts b/packages/schema/src/schemas/unknown.ts new file mode 100644 index 00000000..9dc9deb9 --- /dev/null +++ b/packages/schema/src/schemas/unknown.ts @@ -0,0 +1,80 @@ +/** + * t.unknown schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: any, right: any): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return anyToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(_?: t.any): validate { + validateAny.tag = URI.any + function validateAny() { return true as const } + return validateAny +} +/// validate /// +////////////////////// + +export { unknown_ as unknown } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface unknown_ extends unknown_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function UnknownSchema(src: unknown): src is unknown { return true } +UnknownSchema.tag = URI.unknown +UnknownSchema.def = void 0 as unknown + +const unknown_ = Object_assign( + UnknownSchema, + userDefinitions, +) as unknown_ + +Object_assign(unknown_, userExtensions) + +declare namespace unknown_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.unknown + _type: unknown + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/unknown/core.ts b/packages/schema/src/schemas/unknown/core.ts deleted file mode 100644 index 0a190db3..00000000 --- a/packages/schema/src/schemas/unknown/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { unknown_ as unknown } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface unknown_ extends unknown_.core { - //<%= Types %> -} - -function UnknownSchema(src: unknown): src is unknown { return true } -UnknownSchema.tag = URI.unknown -UnknownSchema.def = void 0 as unknown - -const unknown_ = Object_assign( - UnknownSchema, - userDefinitions, -) as unknown_ - -Object_assign(unknown_, userExtensions) - -declare namespace unknown_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.unknown - _type: unknown - get def(): this['_type'] - } -} diff --git a/packages/schema/src/schemas/unknown/equals.ts b/packages/schema/src/schemas/unknown/equals.ts deleted file mode 100644 index ccd2a780..00000000 --- a/packages/schema/src/schemas/unknown/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: any, right: any): boolean { - return Object_is(left, right) -} diff --git a/packages/schema/src/schemas/unknown/toJsonSchema.ts b/packages/schema/src/schemas/unknown/toJsonSchema.ts deleted file mode 100644 index 8d5be5a0..00000000 --- a/packages/schema/src/schemas/unknown/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } -export function toJsonSchema(): toJsonSchema { - function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } - return anyToJsonSchema -} diff --git a/packages/schema/src/schemas/unknown/validate.ts b/packages/schema/src/schemas/unknown/validate.ts deleted file mode 100644 index 3acaa008..00000000 --- a/packages/schema/src/schemas/unknown/validate.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(_?: t.any): validate { - validateAny.tag = URI.any - function validateAny() { return true as const } - return validateAny -} diff --git a/packages/schema/src/schemas/void.ts b/packages/schema/src/schemas/void.ts new file mode 100644 index 00000000..98d37edf --- /dev/null +++ b/packages/schema/src/schemas/void.ts @@ -0,0 +1,85 @@ +/** + * t.void schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: void, right: void): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function voidToJsonSchema(): void { + return void 0 + } + return voidToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'void' } +export function toString(): 'void' { return 'void' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(voidSchema: t.void): validate { + validateVoid.tag = URI.void + function validateVoid(u: unknown, path = Array.of()) { + return voidSchema(u) || [NullaryErrors.void(u, path)] + } + return validateVoid +} +/// validate /// +////////////////////// + +export { void_ as void, void_ } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface void_ extends void_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function VoidSchema(src: unknown): src is void { return src === void 0 } +VoidSchema.tag = URI.void +VoidSchema.def = void 0 as void + +const void_ = Object_assign( + VoidSchema, + userDefinitions, +) as void_ + +Object_assign(void_, userExtensions) + +declare namespace void_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.void + _type: void + get def(): this['_type'] + } +} diff --git a/packages/schema/src/schemas/void/core.ts b/packages/schema/src/schemas/void/core.ts deleted file mode 100644 index d178213e..00000000 --- a/packages/schema/src/schemas/void/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { void_ as void, void_ } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface void_ extends void_.core { - //<%= Types %> -} - -function VoidSchema(src: unknown): src is void { return src === void 0 } -VoidSchema.tag = URI.void -VoidSchema.def = void 0 as void - -const void_ = Object_assign( - VoidSchema, - userDefinitions, -) as void_ - -Object_assign(void_, userExtensions) - -declare namespace void_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.void - _type: void - get def(): this['_type'] - } -} diff --git a/packages/schema/src/schemas/void/equals.ts b/packages/schema/src/schemas/void/equals.ts deleted file mode 100644 index d11d89e3..00000000 --- a/packages/schema/src/schemas/void/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: void, right: void): boolean { - return Object_is(left, right) -} diff --git a/packages/schema/src/schemas/void/toString.ts b/packages/schema/src/schemas/void/toString.ts deleted file mode 100644 index 487d08b3..00000000 --- a/packages/schema/src/schemas/void/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'void' } -export function toString(): 'void' { return 'void' } diff --git a/packages/schema/src/schemas/void/validate.ts b/packages/schema/src/schemas/void/validate.ts deleted file mode 100644 index 68261f9a..00000000 --- a/packages/schema/src/schemas/void/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { t } from '../../_exports.js' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(voidSchema: t.void): validate { - validateVoid.tag = URI.void - function validateVoid(u: unknown, path = Array.of()) { - return voidSchema(u) || [NullaryErrors.void(u, path)] - } - return validateVoid -} From 3f78ef726aee87ba7dc5c4e1b86276378e541f89 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sun, 13 Apr 2025 15:34:35 -0500 Subject: [PATCH 33/45] fix(generator): fixes self-reference problem in generated schemas --- README.md | 1 + .../src/__generated__/__manifest__.ts | 2 +- packages/registry/src/types.ts | 1 + packages/schema-generator/src/imports.ts | 10 - .../src/__generated__/__manifest__.ts | 2 +- packages/schema/package.json | 13 +- .../schema/src/__generated__/__manifest__.ts | 12 +- .../src/{schemas => __schemas__}/any.ts | 2 +- .../src/{schemas => __schemas__}/array.ts | 20 +- .../src/{schemas => __schemas__}/bigint.ts | 4 +- .../src/{schemas => __schemas__}/boolean.ts | 4 +- .../schema/src/{schemas => __schemas__}/eq.ts | 18 +- .../src/{schemas => __schemas__}/integer.ts | 8 +- .../src/{schemas => __schemas__}/intersect.ts | 24 +- .../src/{schemas => __schemas__}/never.ts | 4 +- .../src/{schemas => __schemas__}/null.ts | 4 +- .../src/{schemas => __schemas__}/number.ts | 8 +- .../src/{schemas => __schemas__}/object.ts | 22 +- .../schema/src/{schemas => __schemas__}/of.ts | 4 +- .../src/{schemas => __schemas__}/optional.ts | 22 +- .../src/{schemas => __schemas__}/record.ts | 22 +- .../src/{schemas => __schemas__}/string.ts | 8 +- .../src/{schemas => __schemas__}/symbol.ts | 4 +- .../src/{schemas => __schemas__}/tuple.ts | 22 +- .../src/{schemas => __schemas__}/undefined.ts | 4 +- .../src/{schemas => __schemas__}/union.ts | 24 +- .../src/{schemas => __schemas__}/unknown.ts | 2 +- .../src/{schemas => __schemas__}/void.ts | 4 +- packages/schema/src/_namespace.ts | 22 -- packages/schema/src/build.ts | 220 ++++++++++++++---- 30 files changed, 309 insertions(+), 208 deletions(-) rename packages/schema/src/{schemas => __schemas__}/any.ts (99%) rename packages/schema/src/{schemas => __schemas__}/array.ts (91%) rename packages/schema/src/{schemas => __schemas__}/bigint.ts (97%) rename packages/schema/src/{schemas => __schemas__}/boolean.ts (96%) rename packages/schema/src/{schemas => __schemas__}/eq.ts (85%) rename packages/schema/src/{schemas => __schemas__}/integer.ts (95%) rename packages/schema/src/{schemas => __schemas__}/intersect.ts (86%) rename packages/schema/src/{schemas => __schemas__}/never.ts (96%) rename packages/schema/src/{schemas => __schemas__}/null.ts (96%) rename packages/schema/src/{schemas => __schemas__}/number.ts (96%) rename packages/schema/src/{schemas => __schemas__}/object.ts (93%) rename packages/schema/src/{schemas => __schemas__}/of.ts (96%) rename packages/schema/src/{schemas => __schemas__}/optional.ts (81%) rename packages/schema/src/{schemas => __schemas__}/record.ts (84%) rename packages/schema/src/{schemas => __schemas__}/string.ts (94%) rename packages/schema/src/{schemas => __schemas__}/symbol.ts (96%) rename packages/schema/src/{schemas => __schemas__}/tuple.ts (92%) rename packages/schema/src/{schemas => __schemas__}/undefined.ts (96%) rename packages/schema/src/{schemas => __schemas__}/union.ts (88%) rename packages/schema/src/{schemas => __schemas__}/unknown.ts (99%) rename packages/schema/src/{schemas => __schemas__}/void.ts (96%) diff --git a/README.md b/README.md index 35e8f03d..f5db2243 100644 --- a/README.md +++ b/README.md @@ -407,6 +407,7 @@ flowchart TD schema-zod-adapter(schema-zod-adapter) -.-> registry(registry) schema(schema) -.-> derive-codec(derive-codec) schema(schema) -.-> derive-equals(derive-equals) + schema(schema) -.-> derive-validators(derive-validators) schema(schema) -.-> registry(registry) schema(schema) -.-> schema-core(schema-core) schema(schema) -.-> schema-generator(schema-generator) diff --git a/packages/derive-equals/src/__generated__/__manifest__.ts b/packages/derive-equals/src/__generated__/__manifest__.ts index 03966b74..3ac6d645 100644 --- a/packages/derive-equals/src/__generated__/__manifest__.ts +++ b/packages/derive-equals/src/__generated__/__manifest__.ts @@ -16,7 +16,7 @@ export default { }, "@traversable": { "generateExports": { - "include": ["**/*.ts"] + "include": ["**/*.ts", "schemas/*.ts"] }, "generateIndex": { "include": ["**/*.ts"] diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index 64c2549f..00f801d5 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -39,6 +39,7 @@ export type PickIfDefined< export type Param = T extends (_: infer I) => unknown ? I : never export type Parameters = T extends (..._: infer I) => unknown ? I : never export type Returns = T extends (_: never) => infer O ? O : never +export type IfUnaryReturns = T extends () => infer O ? O : T export type IfReturns = T extends (_: never) => infer O ? O : T export type Conform = Extract> = [_] extends [never] ? Extract : _ export type Target = S extends (_: any) => _ is infer T ? T : never diff --git a/packages/schema-generator/src/imports.ts b/packages/schema-generator/src/imports.ts index 572de9e9..ed408dbc 100644 --- a/packages/schema-generator/src/imports.ts +++ b/packages/schema-generator/src/imports.ts @@ -83,13 +83,9 @@ export function makeImport(dependency: string, { term, type }: ParsedImports, ma let getDependenciesFromImportsForSchema = (schemaExtensions: ExtensionsBySchemaName[keyof ExtensionsBySchemaName]) => { if (!schemaExtensions) return [] else { - - // console.log('\n\nschemaExtensions\n', JSON.stringify(schemaExtensions, null, 2), '\n') - let xs = Object.values(schemaExtensions) .filter((_) => !!_) .flatMap((_) => Object.keys(_)) - // .flatMap((_) => Object.keys(_).filter((_) => _.startsWith('@traversable/'))) return Array.from(new Set(xs)) } } @@ -103,8 +99,6 @@ export function deduplicateImports(extensionsBySchemaName: ExtensionsBySchemaNam let init: Record = {} let pkgNames = getDependenciesFromImportsForSchema(extension) - console.log('pkgNames', pkgNames) - for (let pkgName of pkgNames) { init[pkgName] = { type: { @@ -122,10 +116,6 @@ export function deduplicateImports(extensionsBySchemaName: ExtensionsBySchemaNam if (!imports) return {} fn.map(imports, (imports, pkgName) => { - - // console.log('imports', imports) - - // if (!`${pkgName}`.startsWith('@traversable/')) return {} if (!imports) return {} let { type, term } = imports diff --git a/packages/schema-to-json-schema/src/__generated__/__manifest__.ts b/packages/schema-to-json-schema/src/__generated__/__manifest__.ts index 361f9694..49479959 100644 --- a/packages/schema-to-json-schema/src/__generated__/__manifest__.ts +++ b/packages/schema-to-json-schema/src/__generated__/__manifest__.ts @@ -17,7 +17,7 @@ export default { }, "@traversable": { "generateExports": { - "include": ["**/*.ts"] + "include": ["**/*.ts", "schemas/*.ts"] }, "generateIndex": { "include": ["**/*.ts"] diff --git a/packages/schema/package.json b/packages/schema/package.json index df63c421..533e9257 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -1,7 +1,7 @@ { "name": "@traversable/schema", "type": "module", - "version": "0.0.0", + "version": "0.0.35", "private": false, "description": "", "license": "MIT", @@ -15,8 +15,15 @@ "email": "ahrjarrett@gmail.com" }, "@traversable": { - "generateExports": { "include": ["**/*.ts"] }, - "generateIndex": { "include": ["**/*.ts"] } + "generateExports": { + "include": [ + "**/*.ts", + "schemas/*.ts" + ] + }, + "generateIndex": { + "include": ["**/*.ts"] + } }, "publishConfig": { "access": "public", diff --git a/packages/schema/src/__generated__/__manifest__.ts b/packages/schema/src/__generated__/__manifest__.ts index c23c1f93..c1d3b16e 100644 --- a/packages/schema/src/__generated__/__manifest__.ts +++ b/packages/schema/src/__generated__/__manifest__.ts @@ -1,7 +1,7 @@ export default { "name": "@traversable/schema", "type": "module", - "version": "0.0.0", + "version": "0.0.35", "private": false, "description": "", "license": "MIT", @@ -15,8 +15,12 @@ export default { "email": "ahrjarrett@gmail.com" }, "@traversable": { - "generateExports": { "include": ["**/*.ts"] }, - "generateIndex": { "include": ["**/*.ts"] } + "generateExports": { + "include": ["**/*.ts", "schemas/*.ts"] + }, + "generateIndex": { + "include": ["**/*.ts"] + } }, "publishConfig": { "access": "public", @@ -40,6 +44,7 @@ export default { "peerDependencies": { "@traversable/derive-codec": "workspace:^", "@traversable/derive-equals": "workspace:^", + "@traversable/derive-validators": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema-core": "workspace:^", "@traversable/schema-generator": "workspace:^", @@ -49,6 +54,7 @@ export default { "devDependencies": { "@traversable/derive-codec": "workspace:^", "@traversable/derive-equals": "workspace:^", + "@traversable/derive-validators": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema-core": "workspace:^", "@traversable/schema-generator": "workspace:^", diff --git a/packages/schema/src/schemas/any.ts b/packages/schema/src/__schemas__/any.ts similarity index 99% rename from packages/schema/src/schemas/any.ts rename to packages/schema/src/__schemas__/any.ts index 3bd32d8b..9166a175 100644 --- a/packages/schema/src/schemas/any.ts +++ b/packages/schema/src/__schemas__/any.ts @@ -1,5 +1,5 @@ /** - * t.any schema + * any_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Equal, Unknown } from '@traversable/registry' diff --git a/packages/schema/src/schemas/array.ts b/packages/schema/src/__schemas__/array.ts similarity index 91% rename from packages/schema/src/schemas/array.ts rename to packages/schema/src/__schemas__/array.ts index f061b8e0..eed1464d 100644 --- a/packages/schema/src/schemas/array.ts +++ b/packages/schema/src/__schemas__/array.ts @@ -1,5 +1,5 @@ /** - * t.array schema + * array schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type * as T from '@traversable/registry' @@ -35,9 +35,9 @@ import { Errors, NullaryErrors } from '@traversable/derive-validators' /// equals /// export type equals = never | Equal -export function equals(arraySchema: t.array): equals -export function equals(arraySchema: t.array): equals -export function equals({ def }: t.array<{ equals: Equal }>): Equal { +export function equals(arraySchema: array): equals +export function equals(arraySchema: array): equals +export function equals({ def }: array<{ equals: Equal }>): Equal { let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is function arrayEquals(l: unknown[], r: unknown[]): boolean { if (Object_is(l, r)) return true @@ -63,7 +63,7 @@ export interface toJsonSchema { > } -export function toJsonSchema>(arraySchema: T): toJsonSchema +export function toJsonSchema>(arraySchema: T): toJsonSchema export function toJsonSchema(arraySchema: T): toJsonSchema export function toJsonSchema( { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, @@ -96,8 +96,8 @@ export interface toString { (): never | `(${ReturnType})[]` } -export function toString(arraySchema: t.array): toString -export function toString(arraySchema: t.array): toString +export function toString(arraySchema: array): toString +export function toString(arraySchema: array): toString export function toString({ def }: { def: unknown }) { function arrayToString() { let body = ( @@ -116,10 +116,10 @@ export function toString({ def }: { def: unknown }) { ////////////////////// /// validate /// export type validate = never | ValidationFn -export function validate(arraySchema: t.array): validate -export function validate(arraySchema: t.array): validate +export function validate(arraySchema: array): validate +export function validate(arraySchema: array): validate export function validate( - { def: { validate = () => true }, minLength, maxLength }: t.array + { def: { validate = () => true }, minLength, maxLength }: array ) { validateArray.tag = URI.array function validateArray(u: unknown, path = Array.of()) { diff --git a/packages/schema/src/schemas/bigint.ts b/packages/schema/src/__schemas__/bigint.ts similarity index 97% rename from packages/schema/src/schemas/bigint.ts rename to packages/schema/src/__schemas__/bigint.ts index 949b7cb7..bb4b337e 100644 --- a/packages/schema/src/schemas/bigint.ts +++ b/packages/schema/src/__schemas__/bigint.ts @@ -1,5 +1,5 @@ /** - * t.bigint schema + * bigint_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Bounds, Equal, Unknown } from '@traversable/registry' @@ -42,7 +42,7 @@ export function toString(): 'bigint' { return 'bigint' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(bigIntSchema: S): validate { +export function validate(bigIntSchema: S): validate { validateBigInt.tag = URI.bigint function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] diff --git a/packages/schema/src/schemas/boolean.ts b/packages/schema/src/__schemas__/boolean.ts similarity index 96% rename from packages/schema/src/schemas/boolean.ts rename to packages/schema/src/__schemas__/boolean.ts index 5fe4c1cf..ebbaac60 100644 --- a/packages/schema/src/schemas/boolean.ts +++ b/packages/schema/src/__schemas__/boolean.ts @@ -1,5 +1,5 @@ /** - * t.boolean schema + * boolean_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Equal, Unknown } from '@traversable/registry' @@ -32,7 +32,7 @@ export function toString(): 'boolean' { return 'boolean' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(booleanSchema: t.boolean): validate { +export function validate(booleanSchema: boolean_): validate { validateBoolean.tag = URI.boolean function validateBoolean(u: unknown, path = Array.of()) { return booleanSchema(true as const) || [NullaryErrors.null(u, path)] diff --git a/packages/schema/src/schemas/eq.ts b/packages/schema/src/__schemas__/eq.ts similarity index 85% rename from packages/schema/src/schemas/eq.ts rename to packages/schema/src/__schemas__/eq.ts index 60b5d209..cdb625fd 100644 --- a/packages/schema/src/schemas/eq.ts +++ b/packages/schema/src/__schemas__/eq.ts @@ -1,5 +1,5 @@ /** - * t.eq schema + * eq schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { @@ -25,17 +25,17 @@ import { Errors } from '@traversable/derive-validators' //////////////////// /// equals /// export type equals = never | Equal -export function equals(eqSchema: t.eq): equals +export function equals(eqSchema: eq): equals export function equals(): Equal { - return (left: unknown, right: unknown) => t.eq(left)(right) + return (left: unknown, right: unknown) => eq(left)(right) } /// equals /// //////////////////// ////////////////////////// /// toJsonSchema /// export interface toJsonSchema { (): { const: T } } -export function toJsonSchema(eqSchema: t.eq): toJsonSchema -export function toJsonSchema({ def }: t.eq) { +export function toJsonSchema(eqSchema: eq): toJsonSchema +export function toJsonSchema({ def }: eq) { function eqToJsonSchema() { return { const: def } } return eqToJsonSchema } @@ -49,8 +49,8 @@ export interface toString { : [T] extends [string] ? `'${T}'` : Key } -export function toString(eqSchema: t.eq): toString -export function toString({ def }: t.eq): () => string { +export function toString(eqSchema: eq): toString +export function toString({ def }: eq): () => string { function eqToString(): string { return typeof def === 'symbol' ? 'symbol' : stringify(def) } @@ -61,8 +61,8 @@ export function toString({ def }: t.eq): () => string { ////////////////////// /// validate /// export type validate = Validate -export function validate(eqSchema: t.eq): validate -export function validate({ def }: t.eq): validate { +export function validate(eqSchema: eq): validate +export function validate({ def }: eq): validate { validateEq.tag = URI.eq function validateEq(u: unknown, path = Array.of()) { let options = getConfig().schema diff --git a/packages/schema/src/schemas/integer.ts b/packages/schema/src/__schemas__/integer.ts similarity index 95% rename from packages/schema/src/schemas/integer.ts rename to packages/schema/src/__schemas__/integer.ts index 684f132f..6594ba9b 100644 --- a/packages/schema/src/schemas/integer.ts +++ b/packages/schema/src/__schemas__/integer.ts @@ -1,5 +1,5 @@ /** - * t.integer schema + * integer schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { @@ -38,8 +38,8 @@ export function equals(left: number, right: number): boolean { /// toJsonSchema /// export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: t.integer): toJsonSchema { +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: integer): toJsonSchema { function integerToJsonSchema() { const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) let bounds: NumericBounds = {} @@ -65,7 +65,7 @@ export function toString(): 'number' { return 'number' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(integerSchema: S): validate { +export function validate(integerSchema: S): validate { validateInteger.tag = URI.integer function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { return integerSchema(u) || [NullaryErrors.integer(u, path)] diff --git a/packages/schema/src/schemas/intersect.ts b/packages/schema/src/__schemas__/intersect.ts similarity index 86% rename from packages/schema/src/schemas/intersect.ts rename to packages/schema/src/__schemas__/intersect.ts index c8c7f32c..fbc80453 100644 --- a/packages/schema/src/schemas/intersect.ts +++ b/packages/schema/src/__schemas__/intersect.ts @@ -1,5 +1,5 @@ /** - * t.intersect schema + * intersect schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { @@ -31,9 +31,9 @@ import type { Validate, ValidationError, Validator } from '@traversable/derive-v //////////////////// /// equals /// export type equals = Equal -export function equals(intersectSchema: t.intersect<[...S]>): equals -export function equals(intersectSchema: t.intersect<[...S]>): equals -export function equals({ def }: t.intersect<{ equals: Equal }[]>): Equal { +export function equals(intersectSchema: intersect<[...S]>): equals +export function equals(intersectSchema: intersect<[...S]>): equals +export function equals({ def }: intersect<{ equals: Equal }[]>): Equal { function intersectEquals(l: unknown, r: unknown): boolean { if (Object_is(l, r)) return true for (let ix = def.length; ix-- !== 0;) @@ -52,9 +52,9 @@ export interface toJsonSchema { } } -export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema -export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema -export function toJsonSchema({ def }: t.intersect): () => {} { +export function toJsonSchema(intersectSchema: intersect): toJsonSchema +export function toJsonSchema(intersectSchema: intersect): toJsonSchema +export function toJsonSchema({ def }: intersect): () => {} { function intersectToJsonSchema() { return { allOf: def.map(getSchema) @@ -72,8 +72,8 @@ export interface toString { : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` } -export function toString(intersectSchema: t.intersect): toString -export function toString({ def }: t.intersect): () => string { +export function toString(intersectSchema: intersect): toString +export function toString({ def }: intersect): () => string { function intersectToString() { return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' } @@ -85,9 +85,9 @@ export function toString({ def }: t.intersect): () => string { /// validate /// export type validate = Validate -export function validate(intersectSchema: t.intersect): validate -export function validate(intersectSchema: t.intersect): validate -export function validate({ def }: t.intersect) { +export function validate(intersectSchema: intersect): validate +export function validate(intersectSchema: intersect): validate +export function validate({ def }: intersect) { validateIntersect.tag = URI.intersect function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { let errors = Array.of() diff --git a/packages/schema/src/schemas/never.ts b/packages/schema/src/__schemas__/never.ts similarity index 96% rename from packages/schema/src/schemas/never.ts rename to packages/schema/src/__schemas__/never.ts index f3d36102..25d455a1 100644 --- a/packages/schema/src/schemas/never.ts +++ b/packages/schema/src/__schemas__/never.ts @@ -1,5 +1,5 @@ /** - * t.never schema + * never_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Equal, Unknown } from '@traversable/registry' @@ -32,7 +32,7 @@ export function toString(): 'never' { return 'never' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(_?: t.never): validate { +export function validate(_?: never_): validate { validateNever.tag = URI.never function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } return validateNever diff --git a/packages/schema/src/schemas/null.ts b/packages/schema/src/__schemas__/null.ts similarity index 96% rename from packages/schema/src/schemas/null.ts rename to packages/schema/src/__schemas__/null.ts index 7d8ef404..e9f0a525 100644 --- a/packages/schema/src/schemas/null.ts +++ b/packages/schema/src/__schemas__/null.ts @@ -1,5 +1,5 @@ /** - * t.null schema + * null_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Equal, Unknown } from '@traversable/registry' @@ -33,7 +33,7 @@ export function toString(): 'null' { return 'null' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(nullSchema: t.null): validate { +export function validate(nullSchema: null_): validate { validateNull.tag = URI.null function validateNull(u: unknown, path = Array.of()) { return nullSchema(u) || [NullaryErrors.null(u, path)] diff --git a/packages/schema/src/schemas/number.ts b/packages/schema/src/__schemas__/number.ts similarity index 96% rename from packages/schema/src/schemas/number.ts rename to packages/schema/src/__schemas__/number.ts index 123011e2..f079373f 100644 --- a/packages/schema/src/schemas/number.ts +++ b/packages/schema/src/__schemas__/number.ts @@ -1,5 +1,5 @@ /** - * t.number schema + * number_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { @@ -36,8 +36,8 @@ export function equals(left: number, right: number): boolean { /// toJsonSchema /// export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: t.number): toJsonSchema { +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: number_): toJsonSchema { function numberToJsonSchema() { const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) let bounds: NumericBounds = {} @@ -63,7 +63,7 @@ export function toString(): 'number' { return 'number' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(numberSchema: S): validate { +export function validate(numberSchema: S): validate { validateNumber.tag = URI.number function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { return numberSchema(u) || [NullaryErrors.number(u, path)] diff --git a/packages/schema/src/schemas/object.ts b/packages/schema/src/__schemas__/object.ts similarity index 93% rename from packages/schema/src/schemas/object.ts rename to packages/schema/src/__schemas__/object.ts index d092654c..d3e7e32a 100644 --- a/packages/schema/src/schemas/object.ts +++ b/packages/schema/src/__schemas__/object.ts @@ -1,5 +1,5 @@ /** - * t.object schema + * object_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type * as T from '@traversable/registry' @@ -44,9 +44,9 @@ import { Errors, NullaryErrors, UnaryErrors } from '@traversable/derive-validato //////////////////// /// equals /// export type equals = never | T.Equal -export function equals(objectSchema: t.object): equals> -export function equals(objectSchema: t.object): equals> -export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { +export function equals(objectSchema: object_): equals> +export function equals(objectSchema: object_): equals> +export function equals({ def }: object_<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { if (Object_is(l, r)) return true if (!l || typeof l !== 'object' || Array_isArray(l)) return false @@ -80,8 +80,8 @@ export interface toJsonSchema(objectSchema: t.object): toJsonSchema -export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema(objectSchema: object_): toJsonSchema +export function toJsonSchema(objectSchema: object_): toJsonSchema export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { const required = Object_keys(def).filter(isRequired(def)) function objectToJsonSchema() { @@ -118,8 +118,8 @@ export interface toString> : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` } -export function toString>(objectSchema: t.object): toString -export function toString({ def }: t.object) { +export function toString>(objectSchema: object_): toString +export function toString({ def }: object_) { function objectToString() { if (!!def && typeof def === 'object') { const entries = Object.entries(def) @@ -152,9 +152,9 @@ let isOptional = has('tag', (tag) => tag === URI.optional) export type validate = never | ValidationFn -export function validate(objectSchema: t.object): validate -export function validate(objectSchema: t.object): validate -export function validate(objectSchema: t.object<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { +export function validate(objectSchema: object_): validate +export function validate(objectSchema: object_): validate +export function validate(objectSchema: object_<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { validateObject.tag = URI.object function validateObject(u: unknown, path_ = Array.of()) { // if (objectSchema(u)) return true diff --git a/packages/schema/src/schemas/of.ts b/packages/schema/src/__schemas__/of.ts similarity index 96% rename from packages/schema/src/schemas/of.ts rename to packages/schema/src/__schemas__/of.ts index b419a2ab..94bae7ec 100644 --- a/packages/schema/src/schemas/of.ts +++ b/packages/schema/src/__schemas__/of.ts @@ -1,5 +1,5 @@ /** - * t.of schema + * of schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Unknown } from '@traversable/registry' @@ -41,7 +41,7 @@ export function toString(): 'unknown' { return 'unknown' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(inlineSchema: t.of): validate { +export function validate(inlineSchema: of): validate { validateInline.tag = URI.inline function validateInline(u: unknown, path = Array.of()) { return inlineSchema(u) || [NullaryErrors.inline(u, path)] diff --git a/packages/schema/src/schemas/optional.ts b/packages/schema/src/__schemas__/optional.ts similarity index 81% rename from packages/schema/src/schemas/optional.ts rename to packages/schema/src/__schemas__/optional.ts index a6428edb..5e62c6da 100644 --- a/packages/schema/src/schemas/optional.ts +++ b/packages/schema/src/__schemas__/optional.ts @@ -1,5 +1,5 @@ /** - * t.optional schema + * optional schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { @@ -27,9 +27,9 @@ import type { Validate, ValidationFn, Validator } from '@traversable/derive-vali //////////////////// /// equals /// export type equals = never | Equal -export function equals(optionalSchema: t.optional): equals -export function equals(optionalSchema: t.optional): equals -export function equals({ def }: t.optional<{ equals: Equal }>): Equal { +export function equals(optionalSchema: optional): equals +export function equals(optionalSchema: optional): equals +export function equals({ def }: optional<{ equals: Equal }>): Equal { return function optionalEquals(l: unknown, r: unknown): boolean { if (Object_is(l, r)) return true return def.equals(l, r) @@ -46,8 +46,8 @@ export interface toJsonSchema { [symbol.optional]: number } -export function toJsonSchema(optionalSchema: t.optional): toJsonSchema -export function toJsonSchema({ def }: t.optional) { +export function toJsonSchema(optionalSchema: optional): toJsonSchema +export function toJsonSchema({ def }: optional) { function optionalToJsonSchema() { return getSchema(def) } optionalToJsonSchema[symbol.optional] = wrapOptional(def) return optionalToJsonSchema @@ -61,8 +61,8 @@ export interface toString { (): never | `(${ReturnType} | undefined)` } -export function toString(optionalSchema: t.optional): toString -export function toString({ def }: t.optional): () => string { +export function toString(optionalSchema: optional): toString +export function toString({ def }: optional): () => string { function optionalToString(): string { return '(' + callToString(def) + ' | undefined)' } @@ -74,9 +74,9 @@ export function toString({ def }: t.optional): () => string { /// validate /// export type validate = Validate -export function validate(optionalSchema: t.optional): validate -export function validate(optionalSchema: t.optional): validate -export function validate({ def }: t.optional): ValidationFn { +export function validate(optionalSchema: optional): validate +export function validate(optionalSchema: optional): validate +export function validate({ def }: optional): ValidationFn { validateOptional.tag = URI.optional validateOptional.optional = 1 function validateOptional(u: unknown, path = Array.of()) { diff --git a/packages/schema/src/schemas/record.ts b/packages/schema/src/__schemas__/record.ts similarity index 84% rename from packages/schema/src/schemas/record.ts rename to packages/schema/src/__schemas__/record.ts index c0b7121e..63d12e6c 100644 --- a/packages/schema/src/schemas/record.ts +++ b/packages/schema/src/__schemas__/record.ts @@ -1,5 +1,5 @@ /** - * t.record schema + * record schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type * as T from '@traversable/registry' @@ -25,9 +25,9 @@ import { NullaryErrors } from '@traversable/derive-validators' //////////////////// /// equals /// export type equals = never | Equal -export function equals(recordSchema: t.record): equals -export function equals(recordSchema: t.record): equals -export function equals({ def }: t.record<{ equals: Equal }>): Equal> { +export function equals(recordSchema: record): equals +export function equals(recordSchema: record): equals +export function equals({ def }: record<{ equals: Equal }>): Equal> { function recordEquals(l: Record, r: Record): boolean { if (Object_is(l, r)) return true if (!l || typeof l !== 'object' || Array_isArray(l)) return false @@ -63,8 +63,8 @@ export interface toJsonSchema { } } -export function toJsonSchema(recordSchema: t.record): toJsonSchema -export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema(recordSchema: record): toJsonSchema +export function toJsonSchema(recordSchema: record): toJsonSchema export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { return function recordToJsonSchema() { return { @@ -82,8 +82,8 @@ export interface toString { (): never | `Record}>` } -export function toString>(recordSchema: S): toString -export function toString(recordSchema: t.record): toString +export function toString>(recordSchema: S): toString +export function toString(recordSchema: record): toString export function toString({ def }: { def: unknown }): () => string { function recordToString() { return `Record` @@ -95,9 +95,9 @@ export function toString({ def }: { def: unknown }): () => string { ////////////////////// /// validate /// export type validate = never | ValidationFn -export function validate(recordSchema: t.record): validate -export function validate(recordSchema: t.record): validate -export function validate({ def: { validate = () => true } }: t.record) { +export function validate(recordSchema: record): validate +export function validate(recordSchema: record): validate +export function validate({ def: { validate = () => true } }: record) { validateRecord.tag = URI.record function validateRecord(u: unknown, path = Array.of()) { if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] diff --git a/packages/schema/src/schemas/string.ts b/packages/schema/src/__schemas__/string.ts similarity index 94% rename from packages/schema/src/schemas/string.ts rename to packages/schema/src/__schemas__/string.ts index 4d795695..deb1dd42 100644 --- a/packages/schema/src/schemas/string.ts +++ b/packages/schema/src/__schemas__/string.ts @@ -1,5 +1,5 @@ /** - * t.string schema + * string_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { @@ -38,8 +38,8 @@ export interface toJsonSchema { (): Force<{ type: 'string' } & PickIfDefined> } -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: t.string): () => { type: 'string' } & Partial { +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: string_): () => { type: 'string' } & Partial { function stringToJsonSchema() { const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null @@ -62,7 +62,7 @@ export function toString(): 'string' { return 'string' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(stringSchema: S): validate { +export function validate(stringSchema: S): validate { validateString.tag = URI.string function validateString(u: unknown, path = Array.of()): true | ValidationError[] { return stringSchema(u) || [NullaryErrors.number(u, path)] diff --git a/packages/schema/src/schemas/symbol.ts b/packages/schema/src/__schemas__/symbol.ts similarity index 96% rename from packages/schema/src/schemas/symbol.ts rename to packages/schema/src/__schemas__/symbol.ts index 199ba57b..201b1ec0 100644 --- a/packages/schema/src/schemas/symbol.ts +++ b/packages/schema/src/__schemas__/symbol.ts @@ -1,5 +1,5 @@ /** - * t.symbol schema + * symbol_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Equal, Unknown } from '@traversable/registry' @@ -33,7 +33,7 @@ export function toString(): 'symbol' { return 'symbol' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(symbolSchema: t.symbol): validate { +export function validate(symbolSchema: symbol_): validate { validateSymbol.tag = URI.symbol function validateSymbol(u: unknown, path = Array.of()) { return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] diff --git a/packages/schema/src/schemas/tuple.ts b/packages/schema/src/__schemas__/tuple.ts similarity index 92% rename from packages/schema/src/schemas/tuple.ts rename to packages/schema/src/__schemas__/tuple.ts index 207b3e0e..3fc13957 100644 --- a/packages/schema/src/schemas/tuple.ts +++ b/packages/schema/src/__schemas__/tuple.ts @@ -1,5 +1,5 @@ /** - * t.tuple schema + * tuple schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { @@ -44,9 +44,9 @@ import { Errors } from '@traversable/derive-validators' /// equals /// export type equals = Equal -export function equals(tupleSchema: t.tuple): equals -export function equals(tupleSchema: t.tuple): equals -export function equals(tupleSchema: t.tuple) { +export function equals(tupleSchema: tuple): equals +export function equals(tupleSchema: tuple): equals +export function equals(tupleSchema: tuple) { function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { if (Object_is(l, r)) return true if (Array_isArray(l)) { @@ -79,8 +79,8 @@ export interface toJsonSchema { } } -export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema -export function toJsonSchema({ def }: t.tuple): () => { +export function toJsonSchema(tupleSchema: tuple): toJsonSchema +export function toJsonSchema({ def }: tuple): () => { type: 'array' items: unknown additionalItems: false @@ -114,8 +114,8 @@ export interface toString { }, ', '>}]` } -export function toString(tupleSchema: t.tuple): toString -export function toString(tupleSchema: t.tuple): () => string { +export function toString(tupleSchema: tuple): toString +export function toString(tupleSchema: tuple): () => string { function stringToString() { return Array_isArray(tupleSchema.def) ? `[${tupleSchema.def.map( @@ -131,9 +131,9 @@ export function toString(tupleSchema: t.tuple): () => string { ////////////////////// /// validate /// export type validate = Validate -export function validate(tupleSchema: t.tuple<[...S]>): validate -export function validate(tupleSchema: t.tuple<[...S]>): validate -export function validate(tupleSchema: t.tuple<[...S]>): Validate { +export function validate(tupleSchema: tuple<[...S]>): validate +export function validate(tupleSchema: tuple<[...S]>): validate +export function validate(tupleSchema: tuple<[...S]>): Validate { validateTuple.tag = URI.tuple function validateTuple(u: unknown, path = Array.of()) { let errors = Array.of() diff --git a/packages/schema/src/schemas/undefined.ts b/packages/schema/src/__schemas__/undefined.ts similarity index 96% rename from packages/schema/src/schemas/undefined.ts rename to packages/schema/src/__schemas__/undefined.ts index 8a3034e3..dfbc9410 100644 --- a/packages/schema/src/schemas/undefined.ts +++ b/packages/schema/src/__schemas__/undefined.ts @@ -1,5 +1,5 @@ /** - * t.undefined schema + * undefined_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Equal, Unknown } from '@traversable/registry' @@ -33,7 +33,7 @@ export function toString(): 'undefined' { return 'undefined' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(undefinedSchema: t.undefined): validate { +export function validate(undefinedSchema: undefined_): validate { validateUndefined.tag = URI.undefined function validateUndefined(u: unknown, path = Array.of()) { return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] diff --git a/packages/schema/src/schemas/union.ts b/packages/schema/src/__schemas__/union.ts similarity index 88% rename from packages/schema/src/schemas/union.ts rename to packages/schema/src/__schemas__/union.ts index 85c1fbdc..fdf07920 100644 --- a/packages/schema/src/schemas/union.ts +++ b/packages/schema/src/__schemas__/union.ts @@ -1,5 +1,5 @@ /** - * t.union schema + * union schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { @@ -26,9 +26,9 @@ import type { Validate, ValidationError, Validator } from '@traversable/derive-v //////////////////// /// equals /// export type equals = Equal -export function equals(unionSchema: t.union<[...S]>): equals -export function equals(unionSchema: t.union<[...S]>): equals -export function equals({ def }: t.union<{ equals: Equal }[]>): Equal { +export function equals(unionSchema: union<[...S]>): equals +export function equals(unionSchema: union<[...S]>): equals +export function equals({ def }: union<{ equals: Equal }[]>): Equal { function unionEquals(l: unknown, r: unknown): boolean { if (Object_is(l, r)) return true for (let ix = def.length; ix-- !== 0;) @@ -45,9 +45,9 @@ export interface toJsonSchema { (): { anyOf: { [I in keyof T]: Returns } } } -export function toJsonSchema(unionSchema: t.union): toJsonSchema -export function toJsonSchema(unionSchema: t.union): toJsonSchema -export function toJsonSchema({ def }: t.union): () => {} { +export function toJsonSchema(unionSchema: union): toJsonSchema +export function toJsonSchema(unionSchema: union): toJsonSchema +export function toJsonSchema({ def }: union): () => {} { return function unionToJsonSchema() { return { anyOf: def.map(getSchema) @@ -64,8 +64,8 @@ export interface toString { : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` } -export function toString(unionSchema: t.union): toString -export function toString({ def }: t.union): () => string { +export function toString(unionSchema: union): toString +export function toString({ def }: union): () => string { function unionToString() { return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' } @@ -77,9 +77,9 @@ export function toString({ def }: t.union): () => string { /// validate /// export type validate = Validate -export function validate(unionSchema: t.union): validate -export function validate(unionSchema: t.union): validate -export function validate({ def }: t.union) { +export function validate(unionSchema: union): validate +export function validate(unionSchema: union): validate +export function validate({ def }: union) { validateUnion.tag = URI.union function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; diff --git a/packages/schema/src/schemas/unknown.ts b/packages/schema/src/__schemas__/unknown.ts similarity index 99% rename from packages/schema/src/schemas/unknown.ts rename to packages/schema/src/__schemas__/unknown.ts index 9dc9deb9..be368504 100644 --- a/packages/schema/src/schemas/unknown.ts +++ b/packages/schema/src/__schemas__/unknown.ts @@ -1,5 +1,5 @@ /** - * t.unknown schema + * unknown_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Equal, Unknown } from '@traversable/registry' diff --git a/packages/schema/src/schemas/void.ts b/packages/schema/src/__schemas__/void.ts similarity index 96% rename from packages/schema/src/schemas/void.ts rename to packages/schema/src/__schemas__/void.ts index 98d37edf..41bd95b3 100644 --- a/packages/schema/src/schemas/void.ts +++ b/packages/schema/src/__schemas__/void.ts @@ -1,5 +1,5 @@ /** - * t.void schema + * void_ schema * made with ᯓᡣ𐭩 by @traversable/schema */ import type { Equal, Unknown } from '@traversable/registry' @@ -35,7 +35,7 @@ export function toString(): 'void' { return 'void' } ////////////////////// /// validate /// export type validate = ValidationFn -export function validate(voidSchema: t.void): validate { +export function validate(voidSchema: void_): validate { validateVoid.tag = URI.void function validateVoid(u: unknown, path = Array.of()) { return voidSchema(u) || [NullaryErrors.void(u, path)] diff --git a/packages/schema/src/_namespace.ts b/packages/schema/src/_namespace.ts index 866430d6..94ca1a4c 100644 --- a/packages/schema/src/_namespace.ts +++ b/packages/schema/src/_namespace.ts @@ -12,25 +12,3 @@ export type { TupleType, ValidateTuple, } from '@traversable/schema-core/namespace' - -export { any } from './schemas/any.js' -export { array } from './schemas/array.js' -export { bigint } from './schemas/bigint.js' -export { boolean } from './schemas/boolean.js' -export { eq } from './schemas/eq.js' -export { integer } from './schemas/integer.js' -export { intersect } from './schemas/intersect.js' -export { of } from './schemas/of.js' -export { never } from './schemas/never.js' -export { null } from './schemas/null.js' -export { number } from './schemas/number.js' -export { object } from './schemas/object.js' -export { optional } from './schemas/optional.js' -export { record } from './schemas/record.js' -export { string } from './schemas/string.js' -export { symbol } from './schemas/symbol.js' -export { tuple } from './schemas/tuple.js' -export { undefined } from './schemas/undefined.js' -export { union } from './schemas/union.js' -export { unknown } from './schemas/unknown.js' -export { void } from './schemas/void.js' diff --git a/packages/schema/src/build.ts b/packages/schema/src/build.ts index dd6959e0..d5ebdfae 100755 --- a/packages/schema/src/build.ts +++ b/packages/schema/src/build.ts @@ -1,7 +1,7 @@ #!/usr/bin/env pnpm dlx tsx +import type { IfUnaryReturns } from '@traversable/registry' import * as path from 'node:path' import * as fs from 'node:fs' -import type { IfReturns } from '@traversable/registry' import { fn } from '@traversable/registry' import { t } from '@traversable/schema-core' import { generateSchemas } from '@traversable/schema-generator' @@ -12,8 +12,13 @@ import { generateSchemas } from '@traversable/schema-generator' * - [x] Pull the .ts files out of `@traversable/schema-core` * - [x] Pull the .ts files out of `@traversable/derive-equals` * - [x] Pull the .ts files out of `@traversable/schema-to-json-schema` - * - [ ] Pull the .ts files out of `@traversable/derive-validators` - * - [ ] Pull the .ts files out of `@traversable/schema-to-string` + * - [x] Pull the .ts files out of `@traversable/derive-validators` + * - [x] Pull the .ts files out of `@traversable/schema-to-string` + * - [x] Read extension config files from `extensions` dir + * - [x] Allow local imports to pass through the parser + * - [x] Write generated schemas to namespace file so they can be used by other schemas + * - [x] Clean up the temp dir + * - [x] Configure the package.json file to export from `__schemas__` */ let CWD = process.cwd() @@ -22,7 +27,8 @@ let PATH = { libsDir: path.join(CWD, 'node_modules', '@traversable'), tempDir: path.join(CWD, 'src', 'temp'), extensionsDir: path.join(CWD, 'src', 'extensions'), - targetDir: path.join(CWD, 'src', 'schemas'), + targetDir: path.join(CWD, 'src', '__schemas__'), + namespaceFile: path.join(CWD, 'src', '_namespace.ts'), } let EXTENSION_FILES_IGNORE_LIST = [ @@ -65,6 +71,30 @@ let removeIgnoredImports = (content: string) => { return content } +let localSchemaNames = { + any: 'any_', + bigint: 'bigint_', + boolean: 'boolean_', + never: 'never_', + null: 'null_', + number: 'number_', + object: 'object_', + string: 'string_', + symbol: 'symbol_', + undefined: 'undefined_', + unknown: 'unknown_', + void: 'void_', + array: 'array', + eq: 'eq', + integer: 'integer', + intersect: 'intersect', + of: 'of', + optional: 'optional', + record: 'record', + tuple: 'tuple', + union: 'union', +} as Record + let TargetReplace = { internal: { /** @@ -84,26 +114,35 @@ let TargetReplace = { coverageDirective: { from: /\s*\/\* v8 ignore .+ \*\//g, to: '', - } + }, + selfReference: (schemaName: string) => { + let localSchemaName = localSchemaNames[schemaName] + return { + from: `t.${schemaName}`, + to: localSchemaName, + } + }, } type Rewrite = (x: string) => string let rewriteCoreInternalImport: Rewrite = (_) => _.replaceAll(TargetReplace.internal.from, TargetReplace.internal.to) let rewriteCoreNamespaceImport: Rewrite = (_) => _.replaceAll(TargetReplace.namespace.from, TargetReplace.namespace.to) let removeCoverageDirectives: Rewrite = (_) => _.replaceAll(TargetReplace.coverageDirective.from, TargetReplace.coverageDirective.to) +let rewriteSelfReferences: (schemaName: string) => Rewrite = (schemaName) => { + let { from, to } = TargetReplace.selfReference(schemaName) + return (_) => _.replaceAll(from, to) +} let isKeyOf = (k: keyof any, t: T): k is keyof T => !!t && (typeof t === 'object' || typeof t === 'function') && k in t type GetTargetFileName = (libName: string, schemaName: string) => `${string}.ts` -type PostProcessor = (sourceFileContent: string) => string +type PostProcessor = (sourceFileContent: string, schemaName: string) => string type LibOptions = t.typeof let LibOptions = t.object({ relativePath: t.string, getTargetFileName: (x): x is GetTargetFileName => typeof x === 'function', - // tempPostProcessor: (x): x is PostProcessor => typeof x === 'function', - postProcessor: (x): x is PostProcessor => typeof x === 'function', // TODO: actually exclude files excludeFiles: t.array(t.string), includeFiles: t.optional(t.array(t.string)), @@ -112,7 +151,10 @@ let LibOptions = t.object({ type BuildOptions = t.typeof let BuildOptions = t.object({ dryRun: t.optional(t.boolean), + postProcessor: (x): x is PostProcessor => typeof x === 'function', + excludeSchemas: t.optional(t.union(t.array(t.string), t.null)), getSourceDir: t.optional((x): x is (() => string) => typeof x === 'function'), + getNamespaceFile: t.optional((x): x is (() => string) => typeof x === 'function'), getTempDir: t.optional((x): x is (() => string) => typeof x === 'function'), getTargetDir: t.optional((x): x is (() => string) => typeof x === 'function'), getExtensionFilesDir: t.optional((x): x is (() => string) => typeof x === 'function'), @@ -120,7 +162,7 @@ let BuildOptions = t.object({ type LibsOptions = never | { libs: Record> } type LibsConfig = never | { libs: Record } -type ParseOptions = never | { [K in keyof T as K extends `get${infer P}` ? Uncapitalize

: K]-?: IfReturns } +type ParseOptions = never | { [K in keyof T as K extends `get${infer P}` ? Uncapitalize

: K]-?: IfUnaryReturns } type BuildConfig = ParseOptions type Options = @@ -137,19 +179,19 @@ let defaultGetTargetFileName = ( : `${libName}.ts` ) satisfies LibOptions['getTargetFileName'] -let defaultPostProcessor = (_: string) => fn.pipe( - _, +let defaultPostProcessor = (content: string, schemaName: string) => fn.pipe( + content, rewriteCoreInternalImport, rewriteCoreNamespaceImport, removeCoverageDirectives, removeIgnoredImports, + rewriteSelfReferences(schemaName), ) let defaultLibOptions = { relativePath: 'src/schemas', excludeFiles: [], getTargetFileName: defaultGetTargetFileName, - postProcessor: defaultPostProcessor, } satisfies LibOptions let defaultLibs = { @@ -162,10 +204,13 @@ let defaultLibs = { let defaultOptions = { dryRun: false, + postProcessor: defaultPostProcessor, + excludeSchemas: null, + getExtensionFilesDir: () => PATH.extensionsDir, + getNamespaceFile: () => PATH.namespaceFile, getSourceDir: () => PATH.libsDir, getTempDir: () => PATH.tempDir, getTargetDir: () => PATH.targetDir, - getExtensionFilesDir: () => PATH.extensionsDir, libs: defaultLibs, } satisfies Required & LibsOptions @@ -173,34 +218,38 @@ function parseLibOptions({ excludeFiles = defaultLibOptions.excludeFiles, relativePath = defaultLibOptions.relativePath, getTargetFileName = defaultLibOptions.getTargetFileName, - postProcessor = defaultLibOptions.postProcessor, includeFiles, }: Partial): LibOptions { return { excludeFiles, relativePath, getTargetFileName, - postProcessor, ...includeFiles && { includeFiles } } } function parseOptions(options: Options): Config function parseOptions({ + dryRun = defaultOptions.dryRun, + excludeSchemas = null, + getExtensionFilesDir = defaultOptions.getExtensionFilesDir, + getNamespaceFile = defaultOptions.getNamespaceFile, getSourceDir = defaultOptions.getSourceDir, - getTempDir = defaultOptions.getTempDir, getTargetDir = defaultOptions.getTargetDir, - getExtensionFilesDir = defaultOptions.getExtensionFilesDir, - dryRun = defaultOptions.dryRun, + getTempDir = defaultOptions.getTempDir, libs, + postProcessor = defaultOptions.postProcessor, }: Options = defaultOptions): Config { return { dryRun, - tempDir: getTempDir(), - sourceDir: getSourceDir(), - targetDir: getTargetDir(), + excludeSchemas, extensionFilesDir: getExtensionFilesDir(), libs: fn.map(libs, parseLibOptions), + namespaceFile: getNamespaceFile(), + postProcessor, + sourceDir: getSourceDir(), + targetDir: getTargetDir(), + tempDir: getTempDir(), } } @@ -209,8 +258,14 @@ let tap = (effect) => (x) => (effect(x), x) let ensureDir - : (dirpath: string) => void - = (dirpath) => !fs.existsSync(dirpath) && fs.mkdirSync(dirpath) + : (dirpath: string, $: Config) => void + = (dirpath, $) => !$.dryRun + ? void (!fs.existsSync(dirpath) && fs.mkdirSync(dirpath)) + : void ( + console.group('[[DRY_RUN]]: `ensureDir`'), + console.debug('mkDir:', dirpath), + console.groupEnd() + ) function copyExtensionFiles($: Config) { if (!fs.existsSync($.extensionFilesDir)) { @@ -226,15 +281,22 @@ function copyExtensionFiles($: Config) { let tempPath = path.join(tempDirPath, 'extension.ts') let sourcePath = path.join($.extensionFilesDir, filename) let content = fs.readFileSync(sourcePath).toString('utf8') - ensureDir(tempDirPath) - fs.writeFileSync(tempPath, content) + ensureDir(tempDirPath, $) + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `copyExtensionFiles`') + console.debug('\ntempPath:\n', tempPath) + console.debug('\ncontent:\n', content) + console.groupEnd() + } else { + fs.writeFileSync(tempPath, content) + } }) } -function buildSchemas($: Config) { +function buildSchemas($: Config): void { let cache = new Set() - return fs.readdirSync( + return void fs.readdirSync( path.join($.sourceDir), { withFileTypes: true }) .filter(({ name }) => Object.keys($.libs).includes(name)) .map( @@ -251,14 +313,17 @@ function buildSchemas($: Config) { fn.map( (schemaFile) => { let sourceFilePath = path.join(schemaFile.parentPath, schemaFile.name) + let sourceFileContent = fs.readFileSync(sourceFilePath).toString('utf8') let targetFileName = LIB.getTargetFileName(LIB_NAME, schemaFile.name) - let tempDirName = schemaFile.name.endsWith('.ts') + let schemaName = schemaFile.name.endsWith('.ts') ? schemaFile.name.slice(0, -'.ts'.length) : schemaFile.name + console.log('schemaName', schemaName) + let targetFilePath = path.join( $.tempDir, - tempDirName, + schemaName, targetFileName ) @@ -269,19 +334,20 @@ function buildSchemas($: Config) { if (!cache.has(tempDirPath) && !$.dryRun) { cache.add(tempDirPath) - ensureDir(tempDirPath) + ensureDir(tempDirPath, $) } - return fn.pipe( - [ - targetFilePath, - fs.readFileSync(sourceFilePath).toString('utf8') - ] satisfies [any, any], - tap(([targetFilePath, content]) => fs.writeFileSync( + if (!$.dryRun) { + fs.writeFileSync( targetFilePath, - content, - )), - ) + sourceFileContent, + ) + } else { + console.group('\n\n[[DRY_RUN]]:: `buildSchemas`') + console.debug('\ntargetFilePath:\n', targetFilePath) + console.debug('\nsourceFileContent:\n', sourceFileContent) + console.groupEnd() + } } ), ) @@ -289,7 +355,7 @@ function buildSchemas($: Config) { ) } -function getSourcePaths($: Config) { +function getSourcePaths($: Config): Record> { if (!fs.existsSync($.tempDir)) { throw Error('[getSourcePaths] Expected temp directory to exist: ' + $.tempDir) } @@ -318,29 +384,81 @@ function createTargetPaths($: Config, sourcePaths: Record>, targets: Record): void { let schemas = generateSchemas(sources, targets) - for (let [target, content] of schemas) { - void fs.writeFileSync(target, defaultPostProcessor(content)) + for (let [target, generatedContent] of schemas) { + let pathSegments = target.split('/') + let fileName = pathSegments[pathSegments.length - 1] + let schemaName = fileName.endsWith('.ts') ? fileName.slice(0, -'.ts'.length) : fileName + let content = $.postProcessor(generatedContent, schemaName) + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `writeSchemas`') + console.debug('\ntarget:\n', target) + console.debug('\ncontent after post-processing:\n', content) + console.groupEnd() + } else { + fs.writeFileSync(target, content) + } + } +} + +function getNamespaceFileContent(previousContent: string, $: Config, sources: Record>) { + let targetDirNames = $.targetDir.split('/') + let targetDirName = targetDirNames[targetDirNames.length - 1] + let lines = Object.keys(sources).map((schemaName) => `export { ${schemaName} } from './${targetDirName}/${schemaName}.js'`) + return previousContent + '\r\n' + lines.join('\n') + '\r\n' +} + +export function writeNamespaceFile($: Config, sources: Record>) { + let content = getNamespaceFileContent(fs.readFileSync($.namespaceFile).toString('utf8'), $, sources) + if (content.includes('export {')) { + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `writeNamespaceFile`') + console.debug('\ntarget file already have term-level exports:\n', content) + console.groupEnd() + } else { + return void 0 + } + } + else if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `writeNamespaceFile`') + console.debug('\nnamespace file path:\n', $.namespaceFile) + console.debug('\nnamespace file content:\n', content) + console.groupEnd() + } else { + fs.writeFileSync($.namespaceFile, content) + } +} + +export function cleanupTempDir($: Config) { + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `cleanupTempDir`') + console.debug('\ntemp dir path:\n', $.tempDir) + console.groupEnd() + } + else { + void fs.rmSync($.tempDir, { force: true, recursive: true }) } } function build(options: Options) { let $ = parseOptions(options) - void ensureDir($.tempDir) + void ensureDir($.tempDir, $) void copyExtensionFiles($) buildSchemas($) let sources = getSourcePaths($) let targets = createTargetPaths($, sources) - if ($.dryRun) return { - sources, - targets, - } - else { - void ensureDir($.targetDir) - void writeSchemas($, sources, targets) - void fs.rmSync($.tempDir, { force: true, recursive: true }) + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `build`') + console.debug('\nsources:\n', sources) + console.debug('\ntargets:\n', targets) + console.groupEnd() } + + void ensureDir($.targetDir, $) + void writeSchemas($, sources, targets) + void writeNamespaceFile($, sources) + void cleanupTempDir($) } build(defaultOptions) From 53fed17576a9139395679a4f6aaa8e607e37f631 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sun, 13 Apr 2025 19:23:17 -0500 Subject: [PATCH 34/45] includes some iffy changes to object type (makes type homomorphic, but might have broken impls elsewhere) --- packages/derive-equals/src/schemas/eq.ts | 7 +- packages/derive-equals/src/schemas/object.ts | 28 +- packages/derive-equals/src/schemas/tuple.ts | 2 +- packages/derive-validators/src/schemas/any.ts | 10 +- .../derive-validators/src/schemas/object.ts | 2 +- .../derive-validators/src/schemas/optional.ts | 2 +- .../derive-validators/src/schemas/tuple.ts | 7 +- .../derive-validators/src/schemas/union.ts | 2 +- .../derive-validators/src/schemas/unknown.ts | 10 +- packages/schema-core/src/schemas/object.ts | 21 +- packages/schema-core/src/types.ts | 2 +- packages/schema-generator/package.json | 13 +- .../schema-generator/src/__schemas__/any.ts | 80 +++ .../schema-generator/src/__schemas__/array.ts | 250 ++++++++ .../src/__schemas__/bigint.ts | 148 +++++ .../src/__schemas__/boolean.ts | 83 +++ .../schema-generator/src/__schemas__/eq.ts | 122 ++++ .../src/__schemas__/integer.ts | 169 +++++ .../src/__schemas__/intersect.ts | 147 +++++ .../schema-generator/src/__schemas__/never.ts | 80 +++ .../schema-generator/src/__schemas__/null.ts | 86 +++ .../src/__schemas__/number.ts | 207 +++++++ .../src/__schemas__/object.ts | 330 ++++++++++ .../schema-generator/src/__schemas__/of.ts | 94 +++ .../src/__schemas__/optional.ts | 135 ++++ .../src/__schemas__/record.ts | 160 +++++ .../src/__schemas__/string.ts | 170 ++++++ .../src/__schemas__/symbol.ts | 83 +++ .../schema-generator/src/__schemas__/tuple.ts | 225 +++++++ .../src/__schemas__/undefined.ts | 83 +++ .../schema-generator/src/__schemas__/union.ts | 144 +++++ .../src/__schemas__/unknown.ts | 80 +++ .../schema-generator/src/__schemas__/void.ts | 85 +++ packages/schema-generator/src/_exports.ts | 2 + packages/schema-generator/src/_namespace.ts | 14 + packages/schema-generator/src/build.ts | 575 ++++++++++++++++++ .../schema-generator/src/extensions/any.ts | 21 + .../schema-generator/src/extensions/array.ts | 20 + .../schema-generator/src/extensions/bigint.ts | 21 + .../src/extensions/boolean.ts | 21 + .../schema-generator/src/extensions/eq.ts | 20 + .../schema-generator/src/extensions/equals.ts | 3 + .../src/extensions/integer.ts | 21 + .../src/extensions/intersect.ts | 20 + .../schema-generator/src/extensions/never.ts | 21 + .../schema-generator/src/extensions/null.ts | 21 + .../schema-generator/src/extensions/number.ts | 21 + .../schema-generator/src/extensions/object.ts | 20 + .../schema-generator/src/extensions/of.ts | 21 + .../src/extensions/optional.ts | 21 + .../schema-generator/src/extensions/record.ts | 20 + .../schema-generator/src/extensions/string.ts | 21 + .../schema-generator/src/extensions/symbol.ts | 21 + .../src/extensions/toJsonSchema.ts | 3 + .../src/extensions/toString.ts | 3 + .../schema-generator/src/extensions/tuple.ts | 20 + .../src/extensions/undefined.ts | 21 + .../schema-generator/src/extensions/union.ts | 20 + .../src/extensions/unknown.ts | 21 + .../src/extensions/validate.ts | 3 + .../schema-generator/src/extensions/void.ts | 21 + .../schema-generator/src/temp/any/core.ts | 36 ++ .../schema-generator/src/temp/any/equals.ts | 7 + .../src/temp/any/extension.ts | 21 + .../src/temp/any/toJsonSchema.ts | 5 + .../schema-generator/src/temp/any/toString.ts | 2 + .../schema-generator/src/temp/any/validate.ts | 10 + .../schema-generator/src/temp/array/core.ts | 128 ++++ .../schema-generator/src/temp/array/equals.ts | 24 + .../src/temp/array/extension.ts | 20 + .../src/temp/array/toJsonSchema.ts | 36 ++ .../src/temp/array/toString.ts | 22 + .../src/temp/array/validate.ts | 27 + .../schema-generator/src/temp/bigint/core.ts | 99 +++ .../src/temp/bigint/equals.ts | 7 + .../src/temp/bigint/extension.ts | 21 + .../src/temp/bigint/toJsonSchema.ts | 7 + .../src/temp/bigint/toString.ts | 2 + .../src/temp/bigint/validate.ts | 13 + .../schema-generator/src/temp/boolean/core.ts | 37 ++ .../src/temp/boolean/equals.ts | 7 + .../src/temp/boolean/extension.ts | 21 + .../src/temp/boolean/toJsonSchema.ts | 5 + .../src/temp/boolean/toString.ts | 2 + .../src/temp/boolean/validate.ts | 12 + packages/schema-generator/src/temp/eq/core.ts | 41 ++ .../schema-generator/src/temp/eq/equals.ts | 11 + .../schema-generator/src/temp/eq/extension.ts | 20 + .../src/temp/eq/toJsonSchema.ts | 8 + .../schema-generator/src/temp/eq/toString.ts | 17 + .../schema-generator/src/temp/eq/validate.ts | 17 + .../schema-generator/src/temp/integer/core.ts | 100 +++ .../src/temp/integer/equals.ts | 7 + .../src/temp/integer/extension.ts | 21 + .../src/temp/integer/toJsonSchema.ts | 23 + .../src/temp/integer/toString.ts | 2 + .../src/temp/integer/validate.ts | 13 + .../src/temp/intersect/core.ts | 50 ++ .../src/temp/intersect/equals.ts | 16 + .../src/temp/intersect/extension.ts | 20 + .../src/temp/intersect/toJsonSchema.ts | 20 + .../src/temp/intersect/toString.ts | 18 + .../src/temp/intersect/validate.ts | 21 + .../schema-generator/src/temp/never/core.ts | 37 ++ .../schema-generator/src/temp/never/equals.ts | 6 + .../src/temp/never/extension.ts | 21 + .../src/temp/never/toJsonSchema.ts | 5 + .../src/temp/never/toString.ts | 2 + .../src/temp/never/validate.ts | 10 + .../schema-generator/src/temp/null/core.ts | 39 ++ .../schema-generator/src/temp/null/equals.ts | 7 + .../src/temp/null/extension.ts | 21 + .../src/temp/null/toJsonSchema.ts | 5 + .../src/temp/null/toString.ts | 2 + .../src/temp/null/validate.ts | 13 + .../schema-generator/src/temp/number/core.ts | 139 +++++ .../src/temp/number/equals.ts | 7 + .../src/temp/number/extension.ts | 21 + .../src/temp/number/toJsonSchema.ts | 23 + .../src/temp/number/toString.ts | 2 + .../src/temp/number/validate.ts | 13 + .../schema-generator/src/temp/object/core.ts | 76 +++ .../src/temp/object/equals.ts | 55 ++ .../src/temp/object/extension.ts | 20 + .../src/temp/object/toJsonSchema.ts | 27 + .../src/temp/object/toString.ts | 42 ++ .../src/temp/object/validate.ts | 110 ++++ packages/schema-generator/src/temp/of/core.ts | 47 ++ .../schema-generator/src/temp/of/equals.ts | 6 + .../schema-generator/src/temp/of/extension.ts | 21 + .../src/temp/of/toJsonSchema.ts | 7 + .../schema-generator/src/temp/of/toString.ts | 2 + .../schema-generator/src/temp/of/validate.ts | 14 + .../src/temp/optional/core.ts | 55 ++ .../src/temp/optional/equals.ts | 13 + .../src/temp/optional/extension.ts | 21 + .../src/temp/optional/toJsonSchema.ts | 19 + .../src/temp/optional/toString.ts | 15 + .../src/temp/optional/validate.ts | 17 + .../schema-generator/src/temp/record/core.ts | 50 ++ .../src/temp/record/equals.ts | 32 + .../src/temp/record/extension.ts | 20 + .../src/temp/record/toJsonSchema.ts | 21 + .../src/temp/record/toString.ts | 17 + .../src/temp/record/validate.ts | 24 + .../schema-generator/src/temp/string/core.ts | 102 ++++ .../src/temp/string/equals.ts | 6 + .../src/temp/string/extension.ts | 21 + .../src/temp/string/toJsonSchema.ts | 22 + .../src/temp/string/toString.ts | 2 + .../src/temp/string/validate.ts | 13 + .../schema-generator/src/temp/symbol/core.ts | 36 ++ .../src/temp/symbol/equals.ts | 7 + .../src/temp/symbol/extension.ts | 21 + .../src/temp/symbol/toJsonSchema.ts | 5 + .../src/temp/symbol/toString.ts | 2 + .../src/temp/symbol/validate.ts | 13 + .../schema-generator/src/temp/tuple/core.ts | 86 +++ .../schema-generator/src/temp/tuple/equals.ts | 27 + .../src/temp/tuple/extension.ts | 20 + .../src/temp/tuple/toJsonSchema.ts | 37 ++ .../src/temp/tuple/toString.ts | 27 + .../src/temp/tuple/validate.ts | 35 ++ .../src/temp/undefined/core.ts | 36 ++ .../src/temp/undefined/equals.ts | 7 + .../src/temp/undefined/extension.ts | 21 + .../src/temp/undefined/toJsonSchema.ts | 5 + .../src/temp/undefined/toString.ts | 2 + .../src/temp/undefined/validate.ts | 13 + .../schema-generator/src/temp/union/core.ts | 50 ++ .../schema-generator/src/temp/union/equals.ts | 16 + .../src/temp/union/extension.ts | 20 + .../src/temp/union/toJsonSchema.ts | 17 + .../src/temp/union/toString.ts | 18 + .../src/temp/union/validate.ts | 26 + .../schema-generator/src/temp/unknown/core.ts | 36 ++ .../src/temp/unknown/equals.ts | 7 + .../src/temp/unknown/extension.ts | 21 + .../src/temp/unknown/toJsonSchema.ts | 5 + .../src/temp/unknown/toString.ts | 2 + .../src/temp/unknown/validate.ts | 10 + .../schema-generator/src/temp/void/core.ts | 36 ++ .../schema-generator/src/temp/void/equals.ts | 7 + .../src/temp/void/extension.ts | 21 + .../src/temp/void/toJsonSchema.ts | 7 + .../src/temp/void/toString.ts | 2 + .../src/temp/void/validate.ts | 13 + packages/schema-generator/tsconfig.build.json | 11 +- packages/schema-generator/tsconfig.src.json | 9 +- packages/schema-generator/tsconfig.test.json | 2 +- .../src/schemas/object.ts | 2 +- .../src/schemas/tuple.ts | 2 +- .../src/schemas/union.ts | 2 +- .../schema-to-string/src/schemas/object.ts | 7 +- .../schema-to-string/src/schemas/tuple.ts | 7 +- .../schema-to-string/src/schemas/union.ts | 2 +- packages/schema/src/__schemas__/any.ts | 10 +- packages/schema/src/__schemas__/eq.ts | 7 +- packages/schema/src/__schemas__/object.ts | 22 +- packages/schema/src/__schemas__/optional.ts | 2 +- packages/schema/src/__schemas__/tuple.ts | 8 +- packages/schema/src/__schemas__/union.ts | 2 +- packages/schema/src/__schemas__/unknown.ts | 10 +- packages/schema/src/build.ts | 17 +- pnpm-lock.yaml | 3 + vite.config.ts | 2 +- 206 files changed, 7129 insertions(+), 82 deletions(-) create mode 100644 packages/schema-generator/src/__schemas__/any.ts create mode 100644 packages/schema-generator/src/__schemas__/array.ts create mode 100644 packages/schema-generator/src/__schemas__/bigint.ts create mode 100644 packages/schema-generator/src/__schemas__/boolean.ts create mode 100644 packages/schema-generator/src/__schemas__/eq.ts create mode 100644 packages/schema-generator/src/__schemas__/integer.ts create mode 100644 packages/schema-generator/src/__schemas__/intersect.ts create mode 100644 packages/schema-generator/src/__schemas__/never.ts create mode 100644 packages/schema-generator/src/__schemas__/null.ts create mode 100644 packages/schema-generator/src/__schemas__/number.ts create mode 100644 packages/schema-generator/src/__schemas__/object.ts create mode 100644 packages/schema-generator/src/__schemas__/of.ts create mode 100644 packages/schema-generator/src/__schemas__/optional.ts create mode 100644 packages/schema-generator/src/__schemas__/record.ts create mode 100644 packages/schema-generator/src/__schemas__/string.ts create mode 100644 packages/schema-generator/src/__schemas__/symbol.ts create mode 100644 packages/schema-generator/src/__schemas__/tuple.ts create mode 100644 packages/schema-generator/src/__schemas__/undefined.ts create mode 100644 packages/schema-generator/src/__schemas__/union.ts create mode 100644 packages/schema-generator/src/__schemas__/unknown.ts create mode 100644 packages/schema-generator/src/__schemas__/void.ts create mode 100644 packages/schema-generator/src/_exports.ts create mode 100644 packages/schema-generator/src/_namespace.ts create mode 100755 packages/schema-generator/src/build.ts create mode 100644 packages/schema-generator/src/extensions/any.ts create mode 100644 packages/schema-generator/src/extensions/array.ts create mode 100644 packages/schema-generator/src/extensions/bigint.ts create mode 100644 packages/schema-generator/src/extensions/boolean.ts create mode 100644 packages/schema-generator/src/extensions/eq.ts create mode 100644 packages/schema-generator/src/extensions/equals.ts create mode 100644 packages/schema-generator/src/extensions/integer.ts create mode 100644 packages/schema-generator/src/extensions/intersect.ts create mode 100644 packages/schema-generator/src/extensions/never.ts create mode 100644 packages/schema-generator/src/extensions/null.ts create mode 100644 packages/schema-generator/src/extensions/number.ts create mode 100644 packages/schema-generator/src/extensions/object.ts create mode 100644 packages/schema-generator/src/extensions/of.ts create mode 100644 packages/schema-generator/src/extensions/optional.ts create mode 100644 packages/schema-generator/src/extensions/record.ts create mode 100644 packages/schema-generator/src/extensions/string.ts create mode 100644 packages/schema-generator/src/extensions/symbol.ts create mode 100644 packages/schema-generator/src/extensions/toJsonSchema.ts create mode 100644 packages/schema-generator/src/extensions/toString.ts create mode 100644 packages/schema-generator/src/extensions/tuple.ts create mode 100644 packages/schema-generator/src/extensions/undefined.ts create mode 100644 packages/schema-generator/src/extensions/union.ts create mode 100644 packages/schema-generator/src/extensions/unknown.ts create mode 100644 packages/schema-generator/src/extensions/validate.ts create mode 100644 packages/schema-generator/src/extensions/void.ts create mode 100644 packages/schema-generator/src/temp/any/core.ts create mode 100644 packages/schema-generator/src/temp/any/equals.ts create mode 100644 packages/schema-generator/src/temp/any/extension.ts create mode 100644 packages/schema-generator/src/temp/any/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/any/toString.ts create mode 100644 packages/schema-generator/src/temp/any/validate.ts create mode 100644 packages/schema-generator/src/temp/array/core.ts create mode 100644 packages/schema-generator/src/temp/array/equals.ts create mode 100644 packages/schema-generator/src/temp/array/extension.ts create mode 100644 packages/schema-generator/src/temp/array/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/array/toString.ts create mode 100644 packages/schema-generator/src/temp/array/validate.ts create mode 100644 packages/schema-generator/src/temp/bigint/core.ts create mode 100644 packages/schema-generator/src/temp/bigint/equals.ts create mode 100644 packages/schema-generator/src/temp/bigint/extension.ts create mode 100644 packages/schema-generator/src/temp/bigint/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/bigint/toString.ts create mode 100644 packages/schema-generator/src/temp/bigint/validate.ts create mode 100644 packages/schema-generator/src/temp/boolean/core.ts create mode 100644 packages/schema-generator/src/temp/boolean/equals.ts create mode 100644 packages/schema-generator/src/temp/boolean/extension.ts create mode 100644 packages/schema-generator/src/temp/boolean/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/boolean/toString.ts create mode 100644 packages/schema-generator/src/temp/boolean/validate.ts create mode 100644 packages/schema-generator/src/temp/eq/core.ts create mode 100644 packages/schema-generator/src/temp/eq/equals.ts create mode 100644 packages/schema-generator/src/temp/eq/extension.ts create mode 100644 packages/schema-generator/src/temp/eq/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/eq/toString.ts create mode 100644 packages/schema-generator/src/temp/eq/validate.ts create mode 100644 packages/schema-generator/src/temp/integer/core.ts create mode 100644 packages/schema-generator/src/temp/integer/equals.ts create mode 100644 packages/schema-generator/src/temp/integer/extension.ts create mode 100644 packages/schema-generator/src/temp/integer/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/integer/toString.ts create mode 100644 packages/schema-generator/src/temp/integer/validate.ts create mode 100644 packages/schema-generator/src/temp/intersect/core.ts create mode 100644 packages/schema-generator/src/temp/intersect/equals.ts create mode 100644 packages/schema-generator/src/temp/intersect/extension.ts create mode 100644 packages/schema-generator/src/temp/intersect/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/intersect/toString.ts create mode 100644 packages/schema-generator/src/temp/intersect/validate.ts create mode 100644 packages/schema-generator/src/temp/never/core.ts create mode 100644 packages/schema-generator/src/temp/never/equals.ts create mode 100644 packages/schema-generator/src/temp/never/extension.ts create mode 100644 packages/schema-generator/src/temp/never/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/never/toString.ts create mode 100644 packages/schema-generator/src/temp/never/validate.ts create mode 100644 packages/schema-generator/src/temp/null/core.ts create mode 100644 packages/schema-generator/src/temp/null/equals.ts create mode 100644 packages/schema-generator/src/temp/null/extension.ts create mode 100644 packages/schema-generator/src/temp/null/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/null/toString.ts create mode 100644 packages/schema-generator/src/temp/null/validate.ts create mode 100644 packages/schema-generator/src/temp/number/core.ts create mode 100644 packages/schema-generator/src/temp/number/equals.ts create mode 100644 packages/schema-generator/src/temp/number/extension.ts create mode 100644 packages/schema-generator/src/temp/number/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/number/toString.ts create mode 100644 packages/schema-generator/src/temp/number/validate.ts create mode 100644 packages/schema-generator/src/temp/object/core.ts create mode 100644 packages/schema-generator/src/temp/object/equals.ts create mode 100644 packages/schema-generator/src/temp/object/extension.ts create mode 100644 packages/schema-generator/src/temp/object/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/object/toString.ts create mode 100644 packages/schema-generator/src/temp/object/validate.ts create mode 100644 packages/schema-generator/src/temp/of/core.ts create mode 100644 packages/schema-generator/src/temp/of/equals.ts create mode 100644 packages/schema-generator/src/temp/of/extension.ts create mode 100644 packages/schema-generator/src/temp/of/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/of/toString.ts create mode 100644 packages/schema-generator/src/temp/of/validate.ts create mode 100644 packages/schema-generator/src/temp/optional/core.ts create mode 100644 packages/schema-generator/src/temp/optional/equals.ts create mode 100644 packages/schema-generator/src/temp/optional/extension.ts create mode 100644 packages/schema-generator/src/temp/optional/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/optional/toString.ts create mode 100644 packages/schema-generator/src/temp/optional/validate.ts create mode 100644 packages/schema-generator/src/temp/record/core.ts create mode 100644 packages/schema-generator/src/temp/record/equals.ts create mode 100644 packages/schema-generator/src/temp/record/extension.ts create mode 100644 packages/schema-generator/src/temp/record/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/record/toString.ts create mode 100644 packages/schema-generator/src/temp/record/validate.ts create mode 100644 packages/schema-generator/src/temp/string/core.ts create mode 100644 packages/schema-generator/src/temp/string/equals.ts create mode 100644 packages/schema-generator/src/temp/string/extension.ts create mode 100644 packages/schema-generator/src/temp/string/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/string/toString.ts create mode 100644 packages/schema-generator/src/temp/string/validate.ts create mode 100644 packages/schema-generator/src/temp/symbol/core.ts create mode 100644 packages/schema-generator/src/temp/symbol/equals.ts create mode 100644 packages/schema-generator/src/temp/symbol/extension.ts create mode 100644 packages/schema-generator/src/temp/symbol/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/symbol/toString.ts create mode 100644 packages/schema-generator/src/temp/symbol/validate.ts create mode 100644 packages/schema-generator/src/temp/tuple/core.ts create mode 100644 packages/schema-generator/src/temp/tuple/equals.ts create mode 100644 packages/schema-generator/src/temp/tuple/extension.ts create mode 100644 packages/schema-generator/src/temp/tuple/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/tuple/toString.ts create mode 100644 packages/schema-generator/src/temp/tuple/validate.ts create mode 100644 packages/schema-generator/src/temp/undefined/core.ts create mode 100644 packages/schema-generator/src/temp/undefined/equals.ts create mode 100644 packages/schema-generator/src/temp/undefined/extension.ts create mode 100644 packages/schema-generator/src/temp/undefined/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/undefined/toString.ts create mode 100644 packages/schema-generator/src/temp/undefined/validate.ts create mode 100644 packages/schema-generator/src/temp/union/core.ts create mode 100644 packages/schema-generator/src/temp/union/equals.ts create mode 100644 packages/schema-generator/src/temp/union/extension.ts create mode 100644 packages/schema-generator/src/temp/union/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/union/toString.ts create mode 100644 packages/schema-generator/src/temp/union/validate.ts create mode 100644 packages/schema-generator/src/temp/unknown/core.ts create mode 100644 packages/schema-generator/src/temp/unknown/equals.ts create mode 100644 packages/schema-generator/src/temp/unknown/extension.ts create mode 100644 packages/schema-generator/src/temp/unknown/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/unknown/toString.ts create mode 100644 packages/schema-generator/src/temp/unknown/validate.ts create mode 100644 packages/schema-generator/src/temp/void/core.ts create mode 100644 packages/schema-generator/src/temp/void/equals.ts create mode 100644 packages/schema-generator/src/temp/void/extension.ts create mode 100644 packages/schema-generator/src/temp/void/toJsonSchema.ts create mode 100644 packages/schema-generator/src/temp/void/toString.ts create mode 100644 packages/schema-generator/src/temp/void/validate.ts diff --git a/packages/derive-equals/src/schemas/eq.ts b/packages/derive-equals/src/schemas/eq.ts index 9df41231..774127f6 100644 --- a/packages/derive-equals/src/schemas/eq.ts +++ b/packages/derive-equals/src/schemas/eq.ts @@ -1,8 +1,11 @@ import type { Equal } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import { laxEquals } from '@traversable/registry' +import type { t } from '@traversable/schema-core' export type equals = never | Equal export function equals(eqSchema: t.eq): equals export function equals(): Equal { - return (left: unknown, right: unknown) => t.eq(left)(right) + return function eqEquals(left: any, right: any) { + return laxEquals(left, right) + } } diff --git a/packages/derive-equals/src/schemas/object.ts b/packages/derive-equals/src/schemas/object.ts index 3cd8134f..ecda0b32 100644 --- a/packages/derive-equals/src/schemas/object.ts +++ b/packages/derive-equals/src/schemas/object.ts @@ -5,7 +5,7 @@ import type { t } from '@traversable/schema-core' export type equals = never | T.Equal export function equals(objectSchema: t.object): equals> export function equals(objectSchema: t.object): equals> -export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { +export function equals({ def }: t.object): equals> { function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { if (Object_is(l, r)) return true if (!l || typeof l !== 'object' || Array_isArray(l)) return false @@ -27,3 +27,29 @@ export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): } return objectEquals } + +// export type equals = never | T.Equal +// export function equals(objectSchema: t.object): equals> +// export function equals(objectSchema: t.object): equals> +// export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { +// function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { +// if (Object_is(l, r)) return true +// if (!l || typeof l !== 'object' || Array_isArray(l)) return false +// if (!r || typeof r !== 'object' || Array_isArray(r)) return false +// for (const k in def) { +// const lHas = Object_hasOwn(l, k) +// const rHas = Object_hasOwn(r, k) +// if (lHas) { +// if (!rHas) return false +// if (!def[k].equals(l[k], r[k])) return false +// } +// if (rHas) { +// if (!lHas) return false +// if (!def[k].equals(l[k], r[k])) return false +// } +// if (!def[k].equals(l[k], r[k])) return false +// } +// return true +// } +// return objectEquals +// } diff --git a/packages/derive-equals/src/schemas/tuple.ts b/packages/derive-equals/src/schemas/tuple.ts index 4d9de15c..66adadaa 100644 --- a/packages/derive-equals/src/schemas/tuple.ts +++ b/packages/derive-equals/src/schemas/tuple.ts @@ -1,6 +1,6 @@ import type { Equal } from '@traversable/registry' import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import type { t } from '@traversable/schema-core' export type equals = Equal diff --git a/packages/derive-validators/src/schemas/any.ts b/packages/derive-validators/src/schemas/any.ts index 9c4298c0..d286c4b5 100644 --- a/packages/derive-validators/src/schemas/any.ts +++ b/packages/derive-validators/src/schemas/any.ts @@ -2,9 +2,9 @@ import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import type { ValidationFn } from '@traversable/derive-validators' -export type validate = ValidationFn -export function validate(_?: t.unknown): validate { - validateUnknown.tag = URI.unknown - function validateUnknown() { return true as const } - return validateUnknown +export type validate = ValidationFn +export function validate(_?: t.any): validate { + validateAny.tag = URI.any + function validateAny() { return true as const } + return validateAny } diff --git a/packages/derive-validators/src/schemas/object.ts b/packages/derive-validators/src/schemas/object.ts index 576d5aaf..eb06d116 100644 --- a/packages/derive-validators/src/schemas/object.ts +++ b/packages/derive-validators/src/schemas/object.ts @@ -27,7 +27,7 @@ export type validate = never | ValidationFn export function validate(objectSchema: t.object): validate export function validate(objectSchema: t.object): validate -export function validate(objectSchema: t.object<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { +export function validate(objectSchema: t.object): validate<{ [x: string]: unknown }> { validateObject.tag = URI.object function validateObject(u: unknown, path_ = Array.of()) { // if (objectSchema(u)) return true diff --git a/packages/derive-validators/src/schemas/optional.ts b/packages/derive-validators/src/schemas/optional.ts index feb93d53..70cd1e9d 100644 --- a/packages/derive-validators/src/schemas/optional.ts +++ b/packages/derive-validators/src/schemas/optional.ts @@ -1,5 +1,5 @@ import { URI } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import type { t } from '@traversable/schema-core' import type { Validate, Validator, ValidationFn } from '@traversable/derive-validators' export type validate = Validate diff --git a/packages/derive-validators/src/schemas/tuple.ts b/packages/derive-validators/src/schemas/tuple.ts index d84e3650..5b065453 100644 --- a/packages/derive-validators/src/schemas/tuple.ts +++ b/packages/derive-validators/src/schemas/tuple.ts @@ -1,5 +1,5 @@ -import { URI, Array_isArray } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import { Array_isArray, has, URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' import { Errors } from '@traversable/derive-validators' @@ -8,11 +8,12 @@ export function validate(tupleSchema: t.tuple<[. export function validate(tupleSchema: t.tuple<[...S]>): validate export function validate(tupleSchema: t.tuple<[...S]>): Validate { validateTuple.tag = URI.tuple + let isOptional = has('tag', (tag) => tag === URI.optional) function validateTuple(u: unknown, path = Array.of()) { let errors = Array.of() if (!Array_isArray(u)) return [Errors.array(u, path)] for (let i = 0; i < tupleSchema.def.length; i++) { - if (!(i in u) && !(t.optional.is(tupleSchema.def[i].validate))) { + if (!(i in u) && !(isOptional(tupleSchema.def[i].validate))) { errors.push(Errors.missingIndex(u, [...path, i])) continue } diff --git a/packages/derive-validators/src/schemas/union.ts b/packages/derive-validators/src/schemas/union.ts index ba69fccf..95b1f0f0 100644 --- a/packages/derive-validators/src/schemas/union.ts +++ b/packages/derive-validators/src/schemas/union.ts @@ -1,5 +1,5 @@ import { URI } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import type { t } from '@traversable/schema-core' import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' export type validate = Validate diff --git a/packages/derive-validators/src/schemas/unknown.ts b/packages/derive-validators/src/schemas/unknown.ts index d286c4b5..9c4298c0 100644 --- a/packages/derive-validators/src/schemas/unknown.ts +++ b/packages/derive-validators/src/schemas/unknown.ts @@ -2,9 +2,9 @@ import type { t } from '@traversable/schema-core' import { URI } from '@traversable/registry' import type { ValidationFn } from '@traversable/derive-validators' -export type validate = ValidationFn -export function validate(_?: t.any): validate { - validateAny.tag = URI.any - function validateAny() { return true as const } - return validateAny +export type validate = ValidationFn +export function validate(_?: t.unknown): validate { + validateUnknown.tag = URI.unknown + function validateUnknown() { return true as const } + return validateUnknown } diff --git a/packages/schema-core/src/schemas/object.ts b/packages/schema-core/src/schemas/object.ts index 0b50af73..ad856e41 100644 --- a/packages/schema-core/src/schemas/object.ts +++ b/packages/schema-core/src/schemas/object.ts @@ -26,7 +26,7 @@ function object_< S extends { [x: string]: SchemaLike }, T extends { [K in keyof S]: Entry } >(schemas: S, options?: Options): object_ -function object_(schemas: { [x: string]: Schema }, options?: Options) { +function object_(schemas: S, options?: Options) { return object_.def(schemas, options) } @@ -64,16 +64,13 @@ declare namespace object_ { _type: object_.type tag: URI.object get def(): S - opt: Optional - req: Required + opt: Optional // TODO: use object_.Opt? + req: Required // TODO: use object_.Req? } - type type< - S, - Opt extends Optional = Optional, - Req extends Required = Required, - T = Force< - & { [K in Req]-?: S[K]['_type' & keyof S[K]] } - & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } - > - > = never | T + type Opt = symbol.optional extends keyof S[K] ? never : K + type Req = symbol.optional extends keyof S[K] ? K : never + type type = Force< + & { [K in keyof S as Opt]-?: S[K]['_type' & keyof S[K]] } + & { [K in keyof S as Req]+?: S[K]['_type' & keyof S[K]] } + > } diff --git a/packages/schema-core/src/types.ts b/packages/schema-core/src/types.ts index 3a62fd38..fd0c8821 100644 --- a/packages/schema-core/src/types.ts +++ b/packages/schema-core/src/types.ts @@ -6,7 +6,7 @@ import type { OPT, REQ } from './label.js' import type { optional } from './schemas/optional.js' export interface top { tag: URI.top, readonly _type: unknown, def: this['_type'] } -export interface bottom { tag: URI.bottom, readonly _type: never, def: this['_type'] } +export interface bottom { tag: URI.bottom, readonly _type: never, def: this['_type'], [symbol.optional]: number } export interface invalid<_Err> extends TypeError<''> { tag: URI.never } export type InvalidItem = never | TypeError<'A required element cannot follow an optional element.'> export type $ = [keyof S] extends [never] ? unknown : S diff --git a/packages/schema-generator/package.json b/packages/schema-generator/package.json index 03f32b88..aa5f75fa 100644 --- a/packages/schema-generator/package.json +++ b/packages/schema-generator/package.json @@ -36,22 +36,29 @@ "bench": "echo NOTHING TO BENCH", "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", - "build:esm": "tsc -b tsconfig.build.json", "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:schemas": "pnpm dlx tsx ./src/build.ts", + "build:schemas:watch": "pnpm dlx tsx --watch ./src/build.ts", "check": "tsc -b tsconfig.json", "clean": "pnpm run \"/^clean:.*/\"", "clean:build": "rm -rf .tsbuildinfo dist build", "clean:deps": "rm -rf node_modules", - "_postinstall": "pnpm dlx tsx ./src/cli.ts", + "postinstall": "pnpm dlx tsx ./src/build.ts", "test": "vitest" }, "peerDependencies": { + "@traversable/derive-validators": "workspace:^", + "@traversable/derive-equals": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema-core": "workspace:^" + "@traversable/schema-core": "workspace:^", + "@traversable/schema-to-json-schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^" }, "devDependencies": { "@clack/prompts": "^0.10.1", "@traversable/derive-validators": "workspace:^", + "@traversable/derive-equals": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema-core": "workspace:^", "@traversable/schema-to-json-schema": "workspace:^", diff --git a/packages/schema-generator/src/__schemas__/any.ts b/packages/schema-generator/src/__schemas__/any.ts new file mode 100644 index 00000000..7ada302b --- /dev/null +++ b/packages/schema-generator/src/__schemas__/any.ts @@ -0,0 +1,80 @@ +/** + * any_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: unknown, right: unknown): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return unknownToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'any' } +export function toString(): 'any' { return 'any' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(_?: any_): validate { + validateAny.tag = URI.any + function validateAny() { return true as const } + return validateAny +} +/// validate /// +////////////////////// + +export { any_ as any } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface any_ extends any_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function AnySchema(src: unknown): src is any { return true } +AnySchema.tag = URI.any +AnySchema.def = void 0 as any + +const any_ = Object_assign( + AnySchema, + userDefinitions, +) as any_ + +Object_assign(any_, userExtensions) + +declare namespace any_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.any + _type: any + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/__schemas__/array.ts b/packages/schema-generator/src/__schemas__/array.ts new file mode 100644 index 00000000..eed1464d --- /dev/null +++ b/packages/schema-generator/src/__schemas__/array.ts @@ -0,0 +1,250 @@ +/** + * array schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type * as T from '@traversable/registry' +import type { + Bounds, + Equal, + Integer, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + array as arrayOf, + Array_isArray, + bindUserExtensions, + carryover, + has, + Math_max, + Math_min, + Number_isSafeInteger, + Object_assign, + Object_is, + URI, + within +} from '@traversable/registry' +import type { Guarded, Schema, SchemaLike } from '../_namespace.js' +import type { of } from './of.js' +import type { t } from '../_exports.js' +import type { SizeBounds } from '@traversable/schema-to-json-schema' +import { hasSchema } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { Errors, NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal + +export function equals(arraySchema: array): equals +export function equals(arraySchema: array): equals +export function equals({ def }: array<{ equals: Equal }>): Equal { + let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is + function arrayEquals(l: unknown[], r: unknown[]): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + let len = l.length + if (len !== r.length) return false + for (let ix = len; ix-- !== 0;) + if (!equals(l[ix], r[ix])) return false + return true + } else return false + } + return arrayEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): never | T.Force< + & { type: 'array', items: T.Returns } + & T.PickIfDefined + > +} + +export function toJsonSchema>(arraySchema: T): toJsonSchema +export function toJsonSchema(arraySchema: T): toJsonSchema +export function toJsonSchema( + { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, +): () => { + type: 'array' + items: unknown + minLength?: number + maxLength?: number +} { + function arrayToJsonSchema() { + let items = hasSchema(def) ? def.toJsonSchema() : def + let out = { + type: 'array' as const, + items, + minLength, + maxLength, + } + if (typeof minLength !== 'number') delete out.minLength + if (typeof maxLength !== 'number') delete out.maxLength + return out + } + return arrayToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType})[]` +} + +export function toString(arraySchema: array): toString +export function toString(arraySchema: array): toString +export function toString({ def }: { def: unknown }) { + function arrayToString() { + let body = ( + !!def + && typeof def === 'object' + && 'toString' in def + && typeof def.toString === 'function' + ) ? def.toString() + : '${string}' + return ('(' + body + ')[]') + } + return arrayToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = never | ValidationFn +export function validate(arraySchema: array): validate +export function validate(arraySchema: array): validate +export function validate( + { def: { validate = () => true }, minLength, maxLength }: array +) { + validateArray.tag = URI.array + function validateArray(u: unknown, path = Array.of()) { + if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] + let errors = Array.of() + if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) + if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) + for (let i = 0, len = u.length; i < len; i++) { + let y = u[i] + let results = validate(y, [...path, i]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateArray +} +/// validate /// +////////////////////// + +/** @internal */ +function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { + return Object_assign(function BoundedArraySchema(u: unknown) { + return Array_isArray(u) && within(bounds)(u.length) + }, carry, array(schema)) +} + +export interface array extends array.core { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export function array(schema: S, readonly: 'readonly'): readonlyArray +export function array(schema: S): array +export function array(schema: S): array>> +export function array(schema: S): array { + return array.def(schema) +} + +export namespace array { + export let userDefinitions: Record = { + } as array + export function def(x: S, prev?: array): array + export function def(x: S, prev?: unknown): array + export function def(x: S, prev?: array): array + export function def(x: unknown, prev?: unknown): {} { + let userExtensions: Record = { + toJsonSchema, + validate, + toString, + equals, + } + const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray + function ArraySchema(src: unknown) { return predicate(src) } + ArraySchema.tag = URI.array + ArraySchema.def = x + ArraySchema.min = function arrayMin(minLength: Min) { + return Object_assign( + boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), + { minLength }, + ) + } + ArraySchema.max = function arrayMax(maxLength: Max) { + return Object_assign( + boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), + { maxLength }, + ) + } + ArraySchema.between = function arrayBetween( + min: Min, + max: Max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max) + ) { + return Object_assign( + boundedArray(x, { gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) + } + if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength + if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength + Object_assign(ArraySchema, userDefinitions) + return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) + } +} + +export declare namespace array { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.array + get def(): S + _type: S['_type' & keyof S][] + minLength?: number + maxLength?: number + min>(minLength: Min): array.Min + max>(maxLength: Max): array.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> + } + type Min + = [Self] extends [{ maxLength: number }] + ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> + : array.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> + : array.max + ; + interface min extends array { minLength: Min } + interface max extends array { maxLength: Max } + interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } + type type = never | T +} + +export const readonlyArray: { + (schema: S): readonlyArray + (schema: S): readonlyArray> +} = array +export interface readonlyArray { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: ReadonlyArray +} diff --git a/packages/schema-generator/src/__schemas__/bigint.ts b/packages/schema-generator/src/__schemas__/bigint.ts new file mode 100644 index 00000000..bb4b337e --- /dev/null +++ b/packages/schema-generator/src/__schemas__/bigint.ts @@ -0,0 +1,148 @@ +/** + * bigint_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Bounds, Equal, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Object_assign, + Object_is, + URI, + withinBig as within +} from '@traversable/registry' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' +import type { t } from '../_exports.js' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: bigint, right: bigint): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function bigintToJsonSchema(): void { + return void 0 + } + return bigintToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'bigint' } +export function toString(): 'bigint' { return 'bigint' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(bigIntSchema: S): validate { + validateBigInt.tag = URI.bigint + function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { + return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] + } + return validateBigInt +} +/// validate /// +////////////////////// + +export { bigint_ as bigint } + +/** @internal */ +function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedBigIntSchema(u: unknown) { + return bigint_(u) && within(bounds)(u) + }, carry, bigint_) +} + +interface bigint_ extends bigint_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +function BigIntSchema(src: unknown) { return typeof src === 'bigint' } +BigIntSchema.tag = URI.bigint +BigIntSchema.def = 0n + +const bigint_ = Object_assign( + BigIntSchema, + userDefinitions, +) as bigint_ + +bigint_.min = function bigIntMin(minimum) { + return Object_assign( + boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +bigint_.max = function bigIntMax(maximum) { + return Object_assign( + boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +bigint_.between = function bigIntBetween( + min, + max, + minimum = (max < min ? max : min), + maximum = (max < min ? min : max), +) { + return Object_assign( + boundedBigInt({ gte: minimum, lte: maximum }), + { minimum, maximum } + ) +} + +Object_assign( + bigint_, + bindUserExtensions(bigint_, userExtensions), +) + +declare namespace bigint_ { + interface core extends bigint_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: bigint + tag: URI.bigint + get def(): this['_type'] + minimum?: bigint + maximum?: bigint + } + type Min + = [Self] extends [{ maximum: bigint }] + ? bigint_.between<[min: X, max: Self['maximum']]> + : bigint_.min + ; + type Max + = [Self] extends [{ minimum: bigint }] + ? bigint_.between<[min: Self['minimum'], max: X]> + : bigint_.max + ; + interface methods { + min(minimum: Min): bigint_.Min + max(maximum: Max): bigint_.Max + between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> + } + interface min extends bigint_ { minimum: Min } + interface max extends bigint_ { maximum: Max } + interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } +} diff --git a/packages/schema-generator/src/__schemas__/boolean.ts b/packages/schema-generator/src/__schemas__/boolean.ts new file mode 100644 index 00000000..ebbaac60 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/boolean.ts @@ -0,0 +1,83 @@ +/** + * boolean_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: boolean, right: boolean): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'boolean' } } +export function toJsonSchema(): toJsonSchema { + function booleanToJsonSchema() { return { type: 'boolean' as const } } + return booleanToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'boolean' } +export function toString(): 'boolean' { return 'boolean' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(booleanSchema: boolean_): validate { + validateBoolean.tag = URI.boolean + function validateBoolean(u: unknown, path = Array.of()) { + return booleanSchema(true as const) || [NullaryErrors.null(u, path)] + } + return validateBoolean +} +/// validate /// +////////////////////// + +export { boolean_ as boolean } + +interface boolean_ extends boolean_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } + +BooleanSchema.tag = URI.boolean +BooleanSchema.def = false + +const boolean_ = Object_assign( + BooleanSchema, + userDefinitions, +) as boolean_ + +Object_assign(boolean_, userExtensions) + +declare namespace boolean_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.boolean + _type: boolean + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/__schemas__/eq.ts b/packages/schema-generator/src/__schemas__/eq.ts new file mode 100644 index 00000000..eb99ab5f --- /dev/null +++ b/packages/schema-generator/src/__schemas__/eq.ts @@ -0,0 +1,122 @@ +/** + * eq schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Key, + Mut, + Mutable, + SchemaOptions as Options, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + applyOptions, + bindUserExtensions, + Equal, + getConfig, + laxEquals, + Object_assign, + URI +} from '@traversable/registry' +import type { t } from '../_exports.js' +import { stringify } from '@traversable/schema-to-string' +import type { Validate } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal +export function equals(eqSchema: eq): equals +export function equals(): Equal { + return function eqEquals(left: any, right: any) { + return laxEquals(left, right) + } +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { const: T } } +export function toJsonSchema(eqSchema: eq): toJsonSchema +export function toJsonSchema({ def }: eq) { + function eqToJsonSchema() { return { const: def } } + return eqToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): [Key] extends [never] + ? [T] extends [symbol] ? 'symbol' : 'symbol' + : [T] extends [string] ? `'${T}'` : Key +} + +export function toString(eqSchema: eq): toString +export function toString({ def }: eq): () => string { + function eqToString(): string { + return typeof def === 'symbol' ? 'symbol' : stringify(def) + } + return eqToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate +export function validate(eqSchema: eq): validate +export function validate({ def }: eq): validate { + validateEq.tag = URI.eq + function validateEq(u: unknown, path = Array.of()) { + let options = getConfig().schema + let equals = options?.eq?.equalsFn || Equal.lax + if (equals(def, u)) return true + else return [Errors.eq(u, path, def)] + } + return validateEq +} +/// validate /// +////////////////////// + +export function eq>(value: V, options?: Options): eq> +export function eq(value: V, options?: Options): eq +export function eq(value: V, options?: Options): eq { + return eq.def(value, options) +} + +export interface eq extends eq.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace eq { + export let userDefinitions: Record = { + } + export function def(value: T, options?: Options): eq + export function def(x: T, $?: Options): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const options = applyOptions($) + const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return predicate(src) } + EqSchema.tag = URI.eq + EqSchema.def = x + Object_assign(EqSchema, eq.userDefinitions) + return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) + } +} + +export declare namespace eq { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.eq + _type: V + get def(): V + } +} diff --git a/packages/schema-generator/src/__schemas__/integer.ts b/packages/schema-generator/src/__schemas__/integer.ts new file mode 100644 index 00000000..6594ba9b --- /dev/null +++ b/packages/schema-generator/src/__schemas__/integer.ts @@ -0,0 +1,169 @@ +/** + * integer schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Bounds, + Equal, + Force, + Integer, + PickIfDefined, + Unknown +} from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_max, + Math_min, + Number_isSafeInteger, + Object_assign, + SameValueNumber, + URI, + within +} from '@traversable/registry' +import type { t } from '../_exports.js' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: integer): toJsonSchema { + function integerToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'integer' as const, + ...bounds, + } + } + return integerToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(integerSchema: S): validate { + validateInteger.tag = URI.integer + function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { + return integerSchema(u) || [NullaryErrors.integer(u, path)] + } + return validateInteger +} +/// validate /// +////////////////////// + +export { integer } + +/** @internal */ +function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedIntegerSchema(u: unknown) { + return integer(u) && within(bounds)(u) + }, carry, integer) +} + +interface integer extends integer.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let userDefinitions: Record = { + equals, + toString, +} + +export let userExtensions: Record = { + toJsonSchema, + validate, +} + +function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } +IntegerSchema.tag = URI.integer +IntegerSchema.def = 0 + +const integer = Object_assign( + IntegerSchema, + userDefinitions, +) as integer + +integer.min = function integerMin(minimum) { + return Object_assign( + boundedInteger({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +integer.max = function integerMax(maximum) { + return Object_assign( + boundedInteger({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +integer.between = function integerBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedInteger({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + integer, + bindUserExtensions(integer, userExtensions), +) + +declare namespace integer { + interface core extends integer.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.integer + get def(): this['_type'] + minimum?: number + maximum?: number + } + interface methods { + min>(minimum: Min): integer.Min + max>(maximum: Max): integer.Max + between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maximum: number }] + ? integer.between<[min: X, max: Self['maximum']]> + : integer.min + type Max + = [Self] extends [{ minimum: number }] + ? integer.between<[min: Self['minimum'], max: X]> + : integer.max + interface min extends integer { minimum: Min } + interface max extends integer { maximum: Max } + interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } +} diff --git a/packages/schema-generator/src/__schemas__/intersect.ts b/packages/schema-generator/src/__schemas__/intersect.ts new file mode 100644 index 00000000..fbc80453 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/intersect.ts @@ -0,0 +1,147 @@ +/** + * intersect schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Equal, + Join, + Returns, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + intersect as intersect$, + isUnknown as isAny, + Object_assign, + Object_is, + URI +} from '@traversable/registry' +import type { + Entry, + IntersectType, + Schema, + SchemaLike +} from '../_namespace.js' +import type { t } from '../_exports.js' +import { getSchema } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(intersectSchema: intersect<[...S]>): equals +export function equals(intersectSchema: intersect<[...S]>): equals +export function equals({ def }: intersect<{ equals: Equal }[]>): Equal { + function intersectEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (!def[ix].equals(l, r)) return false + return true + } + return intersectEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { + allOf: { [I in keyof T]: Returns } + } +} + +export function toJsonSchema(intersectSchema: intersect): toJsonSchema +export function toJsonSchema(intersectSchema: intersect): toJsonSchema +export function toJsonSchema({ def }: intersect): () => {} { + function intersectToJsonSchema() { + return { + allOf: def.map(getSchema) + } + } + return intersectToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): never | [T] extends [readonly []] ? 'unknown' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` +} + +export function toString(intersectSchema: intersect): toString +export function toString({ def }: intersect): () => string { + function intersectToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' + } + return intersectToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate + +export function validate(intersectSchema: intersect): validate +export function validate(intersectSchema: intersect): validate +export function validate({ def }: intersect) { + validateIntersect.tag = URI.intersect + function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results !== true) + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + return errors.length === 0 || errors + } + return validateIntersect +} +/// validate /// +////////////////////// + +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: readonly unknown[]) { + return intersect.def(schemas) +} + +export interface intersect extends intersect.core { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export namespace intersect { + export let userDefinitions: Record = { + } as intersect + export function def(xs: readonly [...T]): intersect + export function def(xs: readonly unknown[]): {} { + let userExtensions: Record = { + toJsonSchema, + validate, + toString, + equals, + } + const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny + function IntersectSchema(src: unknown) { return predicate(src) } + IntersectSchema.tag = URI.intersect + IntersectSchema.def = xs + Object_assign(IntersectSchema, intersect.userDefinitions) + return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) + } +} + +export declare namespace intersect { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.intersect + get def(): S + _type: IntersectType + } + type type> = never | T +} diff --git a/packages/schema-generator/src/__schemas__/never.ts b/packages/schema-generator/src/__schemas__/never.ts new file mode 100644 index 00000000..25d455a1 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/never.ts @@ -0,0 +1,80 @@ +/** + * never_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: never, right: never): boolean { + return false +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): never } +export function toJsonSchema(): toJsonSchema { + function neverToJsonSchema() { return void 0 as never } + return neverToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'never' } +export function toString(): 'never' { return 'never' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(_?: never_): validate { + validateNever.tag = URI.never + function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } + return validateNever +} +/// validate /// +////////////////////// + +export { never_ as never } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface never_ extends never_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function NeverSchema(src: unknown): src is never { return false } +NeverSchema.tag = URI.never; +NeverSchema.def = void 0 as never + +const never_ = Object_assign( + NeverSchema, + userDefinitions, +) as never_ + +Object_assign(never_, userExtensions) + +export declare namespace never_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.never + _type: never + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/__schemas__/null.ts b/packages/schema-generator/src/__schemas__/null.ts new file mode 100644 index 00000000..e9f0a525 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/null.ts @@ -0,0 +1,86 @@ +/** + * null_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: null, right: null): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'null', enum: [null] } } +export function toJsonSchema(): toJsonSchema { + function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } + return nullToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'null' } +export function toString(): 'null' { return 'null' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(nullSchema: null_): validate { + validateNull.tag = URI.null + function validateNull(u: unknown, path = Array.of()) { + return nullSchema(u) || [NullaryErrors.null(u, path)] + } + return validateNull +} +/// validate /// +////////////////////// + +export { null_ as null, null_ } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface null_ extends null_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function NullSchema(src: unknown): src is null { return src === null } +NullSchema.def = null +NullSchema.tag = URI.null + +const null_ = Object_assign( + NullSchema, + userDefinitions, +) as null_ + +Object_assign( + null_, + userExtensions, +) + +declare namespace null_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.null + _type: null + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/__schemas__/number.ts b/packages/schema-generator/src/__schemas__/number.ts new file mode 100644 index 00000000..f079373f --- /dev/null +++ b/packages/schema-generator/src/__schemas__/number.ts @@ -0,0 +1,207 @@ +/** + * number_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Bounds, + Equal, + Force, + PickIfDefined, + Unknown +} from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_max, + Math_min, + Object_assign, + SameValueNumber, + URI, + within +} from '@traversable/registry' +import type { t } from '../_exports.js' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: number_): toJsonSchema { + function numberToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'number' as const, + ...bounds, + } + } + return numberToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(numberSchema: S): validate { + validateNumber.tag = URI.number + function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return numberSchema(u) || [NullaryErrors.number(u, path)] + } + return validateNumber +} +/// validate /// +////////////////////// + +export { number_ as number } + +interface number_ extends number_.core { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let userDefinitions: Record = { + toString, + equals, +} + +export let userExtensions: Record = { + toJsonSchema, + validate, +} + +function NumberSchema(src: unknown) { return typeof src === 'number' } +NumberSchema.tag = URI.number +NumberSchema.def = 0 + +const number_ = Object_assign( + NumberSchema, + userDefinitions, +) as number_ + +number_.min = function numberMin(minimum) { + return Object_assign( + boundedNumber({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +number_.max = function numberMax(maximum) { + return Object_assign( + boundedNumber({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +number_.moreThan = function numberMoreThan(exclusiveMinimum) { + return Object_assign( + boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), + { exclusiveMinimum }, + ) +} +number_.lessThan = function numberLessThan(exclusiveMaximum) { + return Object_assign( + boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), + { exclusiveMaximum }, + ) +} +number_.between = function numberBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedNumber({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + number_, + bindUserExtensions(number_, userExtensions), +) + +function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedNumberSchema(u: unknown) { + return typeof u === 'number' && within(bounds)(u) + }, carry, number_) +} + +declare namespace number_ { + interface core extends number_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.number + get def(): this['_type'] + minimum?: number + maximum?: number + exclusiveMinimum?: number + exclusiveMaximum?: number + } + interface methods { + min(minimum: Min): number_.Min + max(maximum: Max): number_.Max + moreThan(moreThan: Min): ExclusiveMin + lessThan(lessThan: Max): ExclusiveMax + between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.between<[min: X, max: Self['maximum']]> + : number_.min + ; + type Max + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.between<[min: Self['minimum'], max: X]> + : number_.max + ; + type ExclusiveMin + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.maxStrictMin<[min: X, Self['maximum']]> + : number_.moreThan + ; + type ExclusiveMax + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.minStrictMax<[Self['minimum'], min: X]> + : number_.lessThan + ; + interface min extends number_ { minimum: Min } + interface max extends number_ { maximum: Max } + interface moreThan extends number_ { exclusiveMinimum: Min } + interface lessThan extends number_ { exclusiveMaximum: Max } + interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } + interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } + interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } + interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } +} diff --git a/packages/schema-generator/src/__schemas__/object.ts b/packages/schema-generator/src/__schemas__/object.ts new file mode 100644 index 00000000..d85bd88b --- /dev/null +++ b/packages/schema-generator/src/__schemas__/object.ts @@ -0,0 +1,330 @@ +/** + * object_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type * as T from '@traversable/registry' +import type { + Force, + Join, + Returns, + SchemaOptions as Options, + UnionToTuple, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + applyOptions, + Array_isArray, + bindUserExtensions, + fn, + has, + isAnyObject, + object as object$, + Object_assign, + Object_hasOwn, + Object_is, + Object_keys, + record as record$, + symbol, + typeName, + URI +} from '@traversable/registry' +import type { + Entry, + Optional, + Required, + Schema, + SchemaLike +} from '../_namespace.js' +import type { t } from '../_exports.js' +import { getConfig } from '../_exports.js' +import type { RequiredKeys } from '@traversable/schema-to-json-schema' +import { isRequired, property } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { Errors, NullaryErrors, UnaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | T.Equal +export function equals(objectSchema: object_): equals> +export function equals(objectSchema: object_): equals> +export function equals({ def }: object_): equals> { + function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + for (const k in def) { + const lHas = Object_hasOwn(l, k) + const rHas = Object_hasOwn(r, k) + if (lHas) { + if (!rHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (rHas) { + if (!lHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (!def[k].equals(l[k], r[k])) return false + } + return true + } + return objectEquals +} + +// export type equals = never | T.Equal +// export function equals(objectSchema: object_): equals> +// export function equals(objectSchema: object_): equals> +// export function equals({ def }: object_<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { +// function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { +// if (Object_is(l, r)) return true +// if (!l || typeof l !== 'object' || Array_isArray(l)) return false +// if (!r || typeof r !== 'object' || Array_isArray(r)) return false +// for (const k in def) { +// const lHas = Object_hasOwn(l, k) +// const rHas = Object_hasOwn(r, k) +// if (lHas) { +// if (!rHas) return false +// if (!def[k].equals(l[k], r[k])) return false +// } +// if (rHas) { +// if (!lHas) return false +// if (!def[k].equals(l[k], r[k])) return false +// } +// if (!def[k].equals(l[k], r[k])) return false +// } +// return true +// } +// return objectEquals +// } +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema = RequiredKeys> { + (): { + type: 'object' + required: { [I in keyof KS]: KS[I] & string } + properties: { [K in keyof T]: Returns } + } +} + +export function toJsonSchema(objectSchema: object_): toJsonSchema +export function toJsonSchema(objectSchema: object_): toJsonSchema +export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { + const required = Object_keys(def).filter(isRequired(def)) + function objectToJsonSchema() { + return { + type: 'object' as const, + required, + properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), + } + } + return objectToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +/** @internal */ +type Symbol_optional = typeof Symbol_optional +const Symbol_optional: typeof symbol.optional = symbol.optional + +/** @internal */ +const hasOptionalSymbol = (u: unknown): u is { toString(): T } => + !!u && typeof u === 'function' + && Symbol_optional in u + && typeof u[Symbol_optional] === 'number' + +/** @internal */ +const hasToString = (x: unknown): x is { toString(): string } => + !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' + +export interface toString> { + (): never + | [keyof T] extends [never] ? '{}' + /* @ts-expect-error */ + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` +} + + +export function toString>(objectSchema: object_): toString +export function toString({ def }: object_) { + function objectToString() { + if (!!def && typeof def === 'object') { + const entries = Object.entries(def) + if (entries.length === 0) return '{}' + else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" + }: ${hasToString(x) ? x.toString() : 'unknown' + }`).join(', ') + } }` + } + else return '{ [x: string]: unknown }' + } + + return objectToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +/** @internal */ +let isObject = (u: unknown): u is { [x: string]: unknown } => + !!u && typeof u === 'object' && !Array_isArray(u) + +/** @internal */ +let isKeyOf = (k: keyof any, u: T): k is keyof T => + !!u && (typeof u === 'function' || typeof u === 'object') && k in u + +/** @internal */ +let isOptional = has('tag', (tag) => tag === URI.optional) + + +export type validate = never | ValidationFn + +export function validate(objectSchema: object_): validate +export function validate(objectSchema: object_): validate +export function validate(objectSchema: object_): validate<{ [x: string]: unknown }> { + validateObject.tag = URI.object + function validateObject(u: unknown, path_ = Array.of()) { + // if (objectSchema(u)) return true + if (!isObject(u)) return [Errors.object(u, path_)] + let errors = Array.of() + let { schema: { optionalTreatment } } = getConfig() + let keys = Object_keys(objectSchema.def) + if (optionalTreatment === 'exactOptional') { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (Object_hasOwn(u, k) && u[k] === undefined) { + if (isOptional(objectSchema.def[k].validate)) { + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] + errors.push(NullaryErrors[tag](...args)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + errors.push(NullaryErrors[tag](u[k], path, tag)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag].invalid(u[k], path)) + } + errors.push(...results) + } + else if (Object_hasOwn(u, k)) { + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + errors.push(...results) + continue + } else { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + } + } + else { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (!Object_hasOwn(u, k)) { + if (!isOptional(objectSchema.def[k].validate)) { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + else { + if (!Object_hasOwn(u, k)) continue + if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { + if (u[k] === undefined) continue + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let j = 0; j < results.length; j++) { + let result = results[j] + errors.push(result) + continue + } + } + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let l = 0; l < results.length; l++) { + let result = results[l] + errors.push(result) + } + } + } + return errors.length === 0 || errors + } + + return validateObject +} +/// validate /// +////////////////////// + +export { object_ as object } + +function object_< + S extends { [x: string]: Schema }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_< + S extends { [x: string]: SchemaLike }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_(schemas: S, options?: Options) { + return object_.def(schemas, options) +} + +interface object_ extends object_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +namespace object_ { + export let userDefinitions: Record = { + } as object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ + export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const keys = Object_keys(xs) + const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) + const req = keys.filter((k) => !has(symbol.optional)(xs[k])) + const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) + function ObjectSchema(src: unknown) { return predicate(src) } + ObjectSchema.tag = URI.object + ObjectSchema.def = xs + ObjectSchema.opt = opt + ObjectSchema.req = req + Object_assign(ObjectSchema, userDefinitions) + return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) + } +} + +declare namespace object_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: object_.type + tag: URI.object + get def(): S + opt: Optional // TODO: use object_.Opt? + req: Required // TODO: use object_.Req? + } + type Opt = symbol.optional extends keyof S[K] ? never : K + type Req = symbol.optional extends keyof S[K] ? K : never + type type = Force< + & { [K in keyof S as Opt]-?: S[K]['_type' & keyof S[K]] } + & { [K in keyof S as Req]+?: S[K]['_type' & keyof S[K]] } + > +} diff --git a/packages/schema-generator/src/__schemas__/of.ts b/packages/schema-generator/src/__schemas__/of.ts new file mode 100644 index 00000000..94bae7ec --- /dev/null +++ b/packages/schema-generator/src/__schemas__/of.ts @@ -0,0 +1,94 @@ +/** + * of schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Unknown } from '@traversable/registry' +import { Equal, Object_assign, URI } from '@traversable/registry' +import type { + Entry, + Guard, + Guarded, + SchemaLike +} from '../_namespace.js' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: T, right: T): boolean { + return Equal.lax(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function inlineToJsonSchema(): void { + return void 0 + } + return inlineToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(inlineSchema: of): validate { + validateInline.tag = URI.inline + function validateInline(u: unknown, path = Array.of()) { + return inlineSchema(u) || [NullaryErrors.inline(u, path)] + } + return validateInline +} +/// validate /// +////////////////////// + +export interface of extends of.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export function of(typeguard: S): Entry +export function of(typeguard: S): of +export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { + typeguard.def = typeguard + return Object_assign(typeguard, of.prototype) +} + +export namespace of { + export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, + } + export let userExtensions: Record = { + validate, + } + export function def(guard: T): of + export function def(guard: T) { + function InlineSchema(src: unknown) { return guard(src) } + InlineSchema.tag = URI.inline + InlineSchema.def = guard + return InlineSchema + } +} + +export declare namespace of { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: Guarded + tag: URI.inline + get def(): S + } + type type> = never | T +} diff --git a/packages/schema-generator/src/__schemas__/optional.ts b/packages/schema-generator/src/__schemas__/optional.ts new file mode 100644 index 00000000..426e5843 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/optional.ts @@ -0,0 +1,135 @@ +/** + * optional schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Equal, + Force, + Returns, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + has, + isUnknown as isAny, + Object_assign, + Object_is, + optional as optional$, + symbol, + URI +} from '@traversable/registry' +import type { Entry, Schema, SchemaLike } from '../_namespace.js' +import type { t } from '../_exports.js' +import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { Validate, ValidationFn, Validator } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal +export function equals(optionalSchema: optional): equals +export function equals(optionalSchema: optional): equals +export function equals({ def }: optional<{ equals: Equal }>): Equal { + return function optionalEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + return def.equals(l, r) + } +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +type Nullable = Force + +export interface toJsonSchema { + (): Nullable> + [symbol.optional]: number +} + +export function toJsonSchema(optionalSchema: optional): toJsonSchema +export function toJsonSchema({ def }: optional) { + function optionalToJsonSchema() { return getSchema(def) } + optionalToJsonSchema[symbol.optional] = wrapOptional(def) + return optionalToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType} | undefined)` +} + +export function toString(optionalSchema: optional): toString +export function toString({ def }: optional): () => string { + function optionalToString(): string { + return '(' + callToString(def) + ' | undefined)' + } + return optionalToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate + +export function validate(optionalSchema: optional): validate +export function validate(optionalSchema: optional): validate +export function validate({ def }: optional): ValidationFn { + validateOptional.tag = URI.optional + validateOptional.optional = 1 + function validateOptional(u: unknown, path = Array.of()) { + if (u === void 0) return true + return def.validate(u, path) + } + return validateOptional +} +/// validate /// +////////////////////// + +export function optional(schema: S): optional +export function optional(schema: S): optional> +export function optional(schema: S): optional { return optional.def(schema) } + +export interface optional extends optional.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace optional { + export let userDefinitions: Record = { + } + export function def(x: T): optional + export function def(x: T) { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const predicate = _isPredicate(x) ? optional$(x) : isAny + function OptionalSchema(src: unknown) { return predicate(src) } + OptionalSchema.tag = URI.optional + OptionalSchema.def = x + OptionalSchema[symbol.optional] = 1 + Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) + return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) + } + export const is + : (u: unknown) => u is optional + = has('tag', (u) => u === URI.optional) +} + +export declare namespace optional { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.optional + _type: undefined | S['_type' & keyof S] + def: S + [symbol.optional]: number + } + export type type = never | T +} diff --git a/packages/schema-generator/src/__schemas__/record.ts b/packages/schema-generator/src/__schemas__/record.ts new file mode 100644 index 00000000..63d12e6c --- /dev/null +++ b/packages/schema-generator/src/__schemas__/record.ts @@ -0,0 +1,160 @@ +/** + * record schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type * as T from '@traversable/registry' +import type { Equal, Returns, Unknown } from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + isAnyObject, + Object_assign, + Object_hasOwn, + Object_is, + Object_keys, + record as record$, + URI +} from '@traversable/registry' +import type { Entry, Schema, SchemaLike } from '../_namespace.js' +import type { t } from '../_exports.js' +import { getSchema } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal +export function equals(recordSchema: record): equals +export function equals(recordSchema: record): equals +export function equals({ def }: record<{ equals: Equal }>): Equal> { + function recordEquals(l: Record, r: Record): boolean { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + const lhs = Object_keys(l) + const rhs = Object_keys(r) + let len = lhs.length + let k: string + if (len !== rhs.length) return false + for (let ix = len; ix-- !== 0;) { + k = lhs[ix] + if (!Object_hasOwn(r, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + len = rhs.length + for (let ix = len; ix-- !== 0;) { + k = rhs[ix] + if (!Object_hasOwn(l, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + return true + } + return recordEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { + type: 'object' + additionalProperties: T.Returns + } +} + +export function toJsonSchema(recordSchema: record): toJsonSchema +export function toJsonSchema(recordSchema: record): toJsonSchema +export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { + return function recordToJsonSchema() { + return { + type: 'object' as const, + additionalProperties: getSchema(def), + } + } +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + /* @ts-expect-error */ + (): never | `Record}>` +} + +export function toString>(recordSchema: S): toString +export function toString(recordSchema: record): toString +export function toString({ def }: { def: unknown }): () => string { + function recordToString() { + return `Record` + } + return recordToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = never | ValidationFn +export function validate(recordSchema: record): validate +export function validate(recordSchema: record): validate +export function validate({ def: { validate = () => true } }: record) { + validateRecord.tag = URI.record + function validateRecord(u: unknown, path = Array.of()) { + if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] + let errors = Array.of() + let keys = Object_keys(u) + for (let k of keys) { + let y = u[k] + let results = validate(y, [...path, k]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateRecord +} +/// validate /// +////////////////////// + +export function record(schema: S): record +export function record(schema: S): record> +export function record(schema: Schema) { + return record.def(schema) +} + +export interface record extends record.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace record { + export let userDefinitions: Record = { + } + export function def(x: T): record + export function def(x: unknown): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const predicate = _isPredicate(x) ? record$(x) : isAnyObject + function RecordSchema(src: unknown) { return predicate(src) } + RecordSchema.tag = URI.record + RecordSchema.def = x + Object_assign(RecordSchema, record.userDefinitions) + return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) + } +} + +export declare namespace record { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.record + get def(): S + _type: Record + } + export type type> = never | T +} diff --git a/packages/schema-generator/src/__schemas__/string.ts b/packages/schema-generator/src/__schemas__/string.ts new file mode 100644 index 00000000..deb1dd42 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/string.ts @@ -0,0 +1,170 @@ +/** + * string_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Bounds, + Equal, + Force, + Integer, + PickIfDefined, + Unknown +} from '@traversable/registry' +import { + bindUserExtensions, + carryover, + has, + Math_max, + Math_min, + Object_assign, + URI, + within +} from '@traversable/registry' +import type { t } from '../_exports.js' +import type { SizeBounds } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: string, right: string): boolean { + return left === right +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): Force<{ type: 'string' } & PickIfDefined> +} + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: string_): () => { type: 'string' } & Partial { + function stringToJsonSchema() { + const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null + const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null + let out: { type: 'string' } & Partial = { type: 'string' } + minLength !== null && void (out.minLength = minLength) + maxLength !== null && void (out.maxLength = maxLength) + + return out + } + return stringToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'string' } +export function toString(): 'string' { return 'string' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(stringSchema: S): validate { + validateString.tag = URI.string + function validateString(u: unknown, path = Array.of()): true | ValidationError[] { + return stringSchema(u) || [NullaryErrors.number(u, path)] + } + return validateString +} +/// validate /// +////////////////////// + +export { string_ as string } + +/** @internal */ +function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedStringSchema(u: unknown) { + return string_(u) && within(bounds)(u.length) + }, carry, string_) +} + +interface string_ extends string_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let userDefinitions: Record = { + toString, + equals, +} + +export let userExtensions: Record = { + toJsonSchema, + validate, +} + +function StringSchema(src: unknown) { return typeof src === 'string' } +StringSchema.tag = URI.string +StringSchema.def = '' + +const string_ = Object_assign( + StringSchema, + userDefinitions, +) as string_ + +string_.min = function stringMinLength(minLength) { + return Object_assign( + boundedString({ gte: minLength }, carryover(this, 'minLength')), + { minLength }, + ) +} +string_.max = function stringMaxLength(maxLength) { + return Object_assign( + boundedString({ lte: maxLength }, carryover(this, 'maxLength')), + { maxLength }, + ) +} +string_.between = function stringBetween( + min, + max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max)) { + return Object_assign( + boundedString({ gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) +} + +Object_assign( + string_, + bindUserExtensions(string_, userExtensions), +) + +declare namespace string_ { + interface core extends string_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: string + tag: URI.string + get def(): this['_type'] + } + interface methods { + minLength?: number + maxLength?: number + min>(minLength: Min): string_.Min + max>(maxLength: Max): string_.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maxLength: number }] + ? string_.between<[min: Min, max: Self['maxLength']]> + : string_.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? string_.between<[min: Self['minLength'], max: Max]> + : string_.max + ; + interface min extends string_ { minLength: Min } + interface max extends string_ { maxLength: Max } + interface between extends string_ { + minLength: Bounds[0] + maxLength: Bounds[1] + } +} diff --git a/packages/schema-generator/src/__schemas__/symbol.ts b/packages/schema-generator/src/__schemas__/symbol.ts new file mode 100644 index 00000000..201b1ec0 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/symbol.ts @@ -0,0 +1,83 @@ +/** + * symbol_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: symbol, right: symbol): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function symbolToJsonSchema() { return void 0 } + return symbolToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'symbol' } +export function toString(): 'symbol' { return 'symbol' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(symbolSchema: symbol_): validate { + validateSymbol.tag = URI.symbol + function validateSymbol(u: unknown, path = Array.of()) { + return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] + } + return validateSymbol +} +/// validate /// +////////////////////// + +export { symbol_ as symbol } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface symbol_ extends symbol_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } +SymbolSchema.tag = URI.symbol +SymbolSchema.def = Symbol() + +const symbol_ = Object_assign( + SymbolSchema, + userDefinitions, +) as symbol_ + +Object_assign(symbol_, userExtensions) + +declare namespace symbol_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.symbol + _type: symbol + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/__schemas__/tuple.ts b/packages/schema-generator/src/__schemas__/tuple.ts new file mode 100644 index 00000000..eb0603bc --- /dev/null +++ b/packages/schema-generator/src/__schemas__/tuple.ts @@ -0,0 +1,225 @@ +/** + * tuple schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Equal, + Join, + Returns, + SchemaOptions as Options, + TypeError, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + getConfig, + has, + Object_assign, + Object_hasOwn, + Object_is, + parseArgs, + symbol, + tuple as tuple$, + URI +} from '@traversable/registry' +import type { + Entry, + FirstOptionalItem, + invalid, + Schema, + SchemaLike, + TupleType, + ValidateTuple +} from '../_namespace.js' +import type { optional } from './optional.js' +import type { t } from '../_exports.js' +import type { MinItems } from '@traversable/schema-to-json-schema' +import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' +import { hasToString } from '@traversable/schema-to-string' +import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal + +export function equals(tupleSchema: tuple): equals +export function equals(tupleSchema: tuple): equals +export function equals(tupleSchema: tuple) { + function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + for (let ix = tupleSchema.def.length; ix-- !== 0;) { + if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue + if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false + if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false + if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { + if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false + } + } + return true + } + return false + } + return tupleEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { + type: 'array', + items: { [I in keyof T]: Returns } + additionalItems: false + minItems: MinItems + maxItems: T['length' & keyof T] + } +} + +export function toJsonSchema(tupleSchema: tuple): toJsonSchema +export function toJsonSchema({ def }: tuple): () => { + type: 'array' + items: unknown + additionalItems: false + minItems?: {} + maxItems?: number +} { + function tupleToJsonSchema() { + let min = minItems(def) + let max = def.length + let items = applyTupleOptionality(def, { min, max }) + return { + type: 'array' as const, + additionalItems: false as const, + items, + minItems: min, + maxItems: max, + } + } + return tupleToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): never | `[${Join<{ + [I in keyof T]: `${ + /* @ts-expect-error */ + T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType + }` + }, ', '>}]` +} + +export function toString(tupleSchema: tuple): toString +export function toString(tupleSchema: tuple): () => string { + let isOptional = has('tag', (tag) => tag === URI.optional) + function stringToString() { + return Array_isArray(tupleSchema.def) + ? `[${tupleSchema.def.map( + (x) => isOptional(x) + ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` + : hasToString(x) ? x.toString() : 'unknown' + ).join(', ')}]` : 'unknown[]' + } + return stringToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate +export function validate(tupleSchema: tuple<[...S]>): validate +export function validate(tupleSchema: tuple<[...S]>): validate +export function validate(tupleSchema: tuple<[...S]>): Validate { + validateTuple.tag = URI.tuple + let isOptional = has('tag', (tag) => tag === URI.optional) + function validateTuple(u: unknown, path = Array.of()) { + let errors = Array.of() + if (!Array_isArray(u)) return [Errors.array(u, path)] + for (let i = 0; i < tupleSchema.def.length; i++) { + if (!(i in u) && !(isOptional(tupleSchema.def[i].validate))) { + errors.push(Errors.missingIndex(u, [...path, i])) + continue + } + let results = tupleSchema.def[i].validate(u[i], [...path, i]) + if (results !== true) { + for (let j = 0; j < results.length; j++) errors.push(results[j]) + results.push(Errors.arrayElement(u[i], [...path, i])) + } + } + if (u.length > tupleSchema.def.length) { + for (let k = tupleSchema.def.length; k < u.length; k++) { + let excess = u[k] + errors.push(Errors.excessItems(excess, [...path, k])) + } + } + return errors.length === 0 || errors + } + return validateTuple +} +/// validate /// +////////////////////// + +export { tuple } + +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { + return tuple.def(...parseArgs(getConfig().schema, args)) +} + +interface tuple extends tuple.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +namespace tuple { + export let userDefinitions: Record = { + } as tuple + export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple + export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const opt = opt_ || xs.findIndex(has(symbol.optional)) + const options = { + ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) + } satisfies tuple.InternalOptions + const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) + function TupleSchema(src: unknown) { return predicate(src) } + TupleSchema.tag = URI.tuple + TupleSchema.def = xs + TupleSchema.opt = opt + Object_assign(TupleSchema, tuple.userDefinitions) + return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) + } +} + +declare namespace tuple { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.tuple + _type: TupleType + opt: FirstOptionalItem + def: S + } + type type> = never | T + type InternalOptions = { minLength?: number } + type validate = ValidateTuple> + + type from + = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T +} diff --git a/packages/schema-generator/src/__schemas__/undefined.ts b/packages/schema-generator/src/__schemas__/undefined.ts new file mode 100644 index 00000000..dfbc9410 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/undefined.ts @@ -0,0 +1,83 @@ +/** + * undefined_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: undefined, right: undefined): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function undefinedToJsonSchema(): void { return void 0 } + return undefinedToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'undefined' } +export function toString(): 'undefined' { return 'undefined' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(undefinedSchema: undefined_): validate { + validateUndefined.tag = URI.undefined + function validateUndefined(u: unknown, path = Array.of()) { + return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] + } + return validateUndefined +} +/// validate /// +////////////////////// + +export { undefined_ as undefined } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface undefined_ extends undefined_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } +UndefinedSchema.tag = URI.undefined +UndefinedSchema.def = void 0 as undefined + +const undefined_ = Object_assign( + UndefinedSchema, + userDefinitions, +) as undefined_ + +Object_assign(undefined_, userExtensions) + +declare namespace undefined_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.undefined + _type: undefined + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/__schemas__/union.ts b/packages/schema-generator/src/__schemas__/union.ts new file mode 100644 index 00000000..0bf23565 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/union.ts @@ -0,0 +1,144 @@ +/** + * union schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { + Equal, + Join, + Returns, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + isUnknown as isAny, + Object_assign, + Object_is, + union as union$, + URI +} from '@traversable/registry' +import type { Entry, Schema, SchemaLike } from '../_namespace.js' +import type { t } from '../_exports.js' +import { getSchema } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(unionSchema: union<[...S]>): equals +export function equals(unionSchema: union<[...S]>): equals +export function equals({ def }: union<{ equals: Equal }[]>): Equal { + function unionEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (def[ix].equals(l, r)) return true + return false + } + return unionEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { anyOf: { [I in keyof T]: Returns } } +} + +export function toJsonSchema(unionSchema: union): toJsonSchema +export function toJsonSchema(unionSchema: union): toJsonSchema +export function toJsonSchema({ def }: union): () => {} { + return function unionToJsonSchema() { + return { + anyOf: def.map(getSchema) + } + } +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` +} + +export function toString(unionSchema: union): toString +export function toString({ def }: union): () => string { + function unionToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' + } + return unionToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate + +export function validate(unionSchema: union): validate +export function validate(unionSchema: union): validate +export function validate({ def }: union) { + validateUnion.tag = URI.union + function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { + // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results === true) { + // validateUnion.optional = 0 + return true + } + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + // validateUnion.optional = 0 + return errors.length === 0 || errors + } + return validateUnion +} +/// validate /// +////////////////////// + +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: unknown[]) { + return union.def(schemas) +} + +export interface union extends union.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace union { + export let userDefinitions: Record = { + } as Partial> + export function def(xs: T): union + export function def(xs: unknown[]) { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const predicate = xs.every(_isPredicate) ? union$(xs) : isAny + function UnionSchema(src: unknown): src is unknown { return predicate(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.userDefinitions) + return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) + } +} + +export declare namespace union { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.union + _type: union.type + get def(): S + } + type type = never | T +} diff --git a/packages/schema-generator/src/__schemas__/unknown.ts b/packages/schema-generator/src/__schemas__/unknown.ts new file mode 100644 index 00000000..caaf06c6 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/unknown.ts @@ -0,0 +1,80 @@ +/** + * unknown_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: any, right: any): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return anyToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(_?: unknown_): validate { + validateUnknown.tag = URI.unknown + function validateUnknown() { return true as const } + return validateUnknown +} +/// validate /// +////////////////////// + +export { unknown_ as unknown } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface unknown_ extends unknown_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function UnknownSchema(src: unknown): src is unknown { return true } +UnknownSchema.tag = URI.unknown +UnknownSchema.def = void 0 as unknown + +const unknown_ = Object_assign( + UnknownSchema, + userDefinitions, +) as unknown_ + +Object_assign(unknown_, userExtensions) + +declare namespace unknown_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.unknown + _type: unknown + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/__schemas__/void.ts b/packages/schema-generator/src/__schemas__/void.ts new file mode 100644 index 00000000..41bd95b3 --- /dev/null +++ b/packages/schema-generator/src/__schemas__/void.ts @@ -0,0 +1,85 @@ +/** + * void_ schema + * made with ᯓᡣ𐭩 by @traversable/schema + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../_exports.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: void, right: void): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function voidToJsonSchema(): void { + return void 0 + } + return voidToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'void' } +export function toString(): 'void' { return 'void' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(voidSchema: void_): validate { + validateVoid.tag = URI.void + function validateVoid(u: unknown, path = Array.of()) { + return voidSchema(u) || [NullaryErrors.void(u, path)] + } + return validateVoid +} +/// validate /// +////////////////////// + +export { void_ as void, void_ } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface void_ extends void_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function VoidSchema(src: unknown): src is void { return src === void 0 } +VoidSchema.tag = URI.void +VoidSchema.def = void 0 as void + +const void_ = Object_assign( + VoidSchema, + userDefinitions, +) as void_ + +Object_assign(void_, userExtensions) + +declare namespace void_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.void + _type: void + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/_exports.ts b/packages/schema-generator/src/_exports.ts new file mode 100644 index 00000000..6cbf2955 --- /dev/null +++ b/packages/schema-generator/src/_exports.ts @@ -0,0 +1,2 @@ +export * as t from './_namespace.js' +export { getConfig } from '@traversable/schema-core' diff --git a/packages/schema-generator/src/_namespace.ts b/packages/schema-generator/src/_namespace.ts new file mode 100644 index 00000000..94ca1a4c --- /dev/null +++ b/packages/schema-generator/src/_namespace.ts @@ -0,0 +1,14 @@ +export type { + Entry, + FirstOptionalItem, + IntersectType, + Guard, + Guarded, + invalid, + Optional, + Required, + Schema, + SchemaLike, + TupleType, + ValidateTuple, +} from '@traversable/schema-core/namespace' diff --git a/packages/schema-generator/src/build.ts b/packages/schema-generator/src/build.ts new file mode 100755 index 00000000..fc5f0000 --- /dev/null +++ b/packages/schema-generator/src/build.ts @@ -0,0 +1,575 @@ +#!/usr/bin/env pnpm dlx tsx +import type { IfUnaryReturns } from '@traversable/registry' +import * as path from 'node:path' +import * as fs from 'node:fs' +import { fn } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import { generateSchemas } from '@traversable/schema-generator' + +/** + * ## TODO + * + * - [x] Pull the .ts files out of `@traversable/schema-core` + * - [x] Pull the .ts files out of `@traversable/derive-equals` + * - [x] Pull the .ts files out of `@traversable/schema-to-json-schema` + * - [x] Pull the .ts files out of `@traversable/derive-validators` + * - [x] Pull the .ts files out of `@traversable/schema-to-string` + * - [x] Read extension config files from `extensions` dir + * - [x] Allow local imports to pass through the parser + * - [x] Write generated schemas to namespace file so they can be used by other schemas + * - [x] Clean up the temp dir + * - [x] Configure the package.json file to export from `__schemas__` + */ + +let CWD = process.cwd() + +let PATH = { + sourceDir: path.join(CWD, 'node_modules', '@traversable'), + tempDir: path.join(CWD, 'src', 'temp'), + extensionsDir: path.join(CWD, 'src', 'extensions'), + targetDir: path.join(CWD, 'src', '__schemas__'), + namespaceFile: path.join(CWD, 'src', '_namespace.ts'), +} + +let EXTENSION_FILES_IGNORE_LIST = [ + 'equals.ts', + 'toJsonSchema.ts', + 'toString.ts', + 'validate.ts', +] + +/** + * TODO: Derive this list from the {@link EXTENSION_FILES_IGNORE_LIST ignore list} + */ +let REMOVE_IMPORTS_LIST = [ + /.*equals.js'\n/, + /.*toJsonSchema.js'\n/, + /.*toString.js'\n/, + /.*validate.js'\n/, +] + +type Library = typeof Library[keyof typeof Library] +let Library = { + Core: 'schema-core', + Equals: 'derive-equals', + ToJsonSchema: 'schema-to-json-schema', + ToString: 'schema-to-string', + Validators: 'derive-validators', +} as const + +let LIB_NAME_TO_TARGET_FILENAME = { + [Library.Core]: 'core', + [Library.Equals]: 'equals', + [Library.ToJsonSchema]: 'toJsonSchema', + [Library.Validators]: 'validate', + [Library.ToString]: 'toString', +} as const satisfies Record + +let removeIgnoredImports = (content: string) => { + for (let ignore of REMOVE_IMPORTS_LIST) + content = content.replace(ignore, '') + return content +} + +let localSchemaNames = { + any: 'any_', + bigint: 'bigint_', + boolean: 'boolean_', + never: 'never_', + null: 'null_', + number: 'number_', + object: 'object_', + string: 'string_', + symbol: 'symbol_', + undefined: 'undefined_', + unknown: 'unknown_', + void: 'void_', + array: 'array', + eq: 'eq', + integer: 'integer', + intersect: 'intersect', + of: 'of', + optional: 'optional', + record: 'record', + tuple: 'tuple', + union: 'union', +} as Record + +let TargetReplace = { + internal: { + /** + * @example + * // from: + * import type { Guarded, Schema, SchemaLike } from '../../_namespace.js' + * // to: + * import type { Guarded, Schema, SchemaLike } from '../namespace.js' + */ + from: /'(\.\.\/)namespace.js'/g, + to: '\'../_namespace.js\'', + }, + namespace: { + from: /'@traversable\/schema-core'/g, + to: '\'../_exports.js\'', + }, + coverageDirective: { + from: /\s*\/\* v8 ignore .+ \*\//g, + to: '', + }, + selfReference: (schemaName: string) => { + let localSchemaName = localSchemaNames[schemaName] + return { + from: `t.${schemaName}`, + to: localSchemaName, + } + }, +} + +type Rewrite = (x: string) => string +let rewriteCoreInternalImport: Rewrite = (_) => _.replaceAll(TargetReplace.internal.from, TargetReplace.internal.to) +let rewriteCoreNamespaceImport: Rewrite = (_) => _.replaceAll(TargetReplace.namespace.from, TargetReplace.namespace.to) +let removeCoverageDirectives: Rewrite = (_) => _.replaceAll(TargetReplace.coverageDirective.from, TargetReplace.coverageDirective.to) +let rewriteSelfReferences: (schemaName: string) => Rewrite = (schemaName) => { + let { from, to } = TargetReplace.selfReference(schemaName) + return (_) => _.replaceAll(from, to) +} + +let isKeyOf = (k: keyof any, t: T): k is keyof T => + !!t && (typeof t === 'object' || typeof t === 'function') && k in t + +type GetTargetFileName = (libName: string, schemaName: string) => `${string}.ts` +type PostProcessor = (sourceFileContent: string, schemaName: string) => string + +type LibOptions = t.typeof +let LibOptions = t.object({ + /** + * ## {@link LibOptions.def.relativePath `LibOptions.relativePath`} + */ + relativePath: t.string, + /** + * ## {@link LibOptions.def.getTargetFileName `LibOptions.getTargetFileName`} + */ + getTargetFileName: (x): x is GetTargetFileName => typeof x === 'function', + // TODO: actually exclude files + /** + * ## {@link LibOptions.def.excludeFiles `LibOptions.excludeFiles`} + */ + excludeFiles: t.array(t.string), + /** + * ## {@link LibOptions.def.includeFiles `LibOptions.includeFiles`} + */ + includeFiles: t.optional(t.array(t.string)), +}) + +type BuildOptions = t.typeof +let BuildOptions = t.object({ + /** + * ## {@link BuildOptions.def.dryRun `Options.dryRun`} + * + * Execute the build as a dry run. Will read files and directory content, + * but won't write anything to disc. + */ + dryRun: t.optional(t.boolean), + /** + * ## {@link BuildOptions.def.extensionFiles `Options.extensionFiles`} + * + * An array of string 2-tuples containing schema name, and extension + * file content encoded as a utf-8 string. + */ + extensionFiles: t.array(t.tuple(t.string, t.string)), + /** + * ## {@link BuildOptions.def.skipCleanup `Options.skipCleanup`} + */ + skipCleanup: t.optional(t.boolean), + /** + * ## {@link BuildOptions.def.postProcessor `Options.postProcessor`} + * + * A function to run over every generated file before writing to disc. + */ + postProcessor: (x): x is PostProcessor => typeof x === 'function', + /** + * ## {@link BuildOptions.def.excludeSchemas `Options.excludeSchemas`} + */ + excludeSchemas: t.optional(t.union(t.array(t.string), t.null)), + /** + * ## {@link BuildOptions.def.getSourceDir `Options.getSourceDir`} + */ + getSourceDir: t.optional((x): x is (() => string) => typeof x === 'function'), + /** + * ## {@link BuildOptions.def.getNamespaceFile `Options.getNamespaceFile`} + */ + getNamespaceFile: t.optional((x): x is (() => string) => typeof x === 'function'), + /** + * ## {@link BuildOptions.def.getTempDir `Options.getTempDir`} + */ + getTempDir: t.optional((x): x is (() => string) => typeof x === 'function'), + /** + * ## {@link BuildOptions.def.getTargetDir `Options.getTargetDir`} + */ + getTargetDir: t.optional((x): x is (() => string) => typeof x === 'function'), + /** + * ## {@link BuildOptions.def.getExtensionFilesDir `Options.getExtensionFilesDir`} + */ + getExtensionFilesDir: t.optional((x): x is (() => string) => typeof x === 'function'), +}) + +type LibsOptions = never | { libs: Record> } +type LibsConfig = never | { libs: Record } +type ParseOptions = never | { [K in keyof T as K extends `get${infer P}` ? Uncapitalize

: K]-?: IfUnaryReturns } +type BuildConfig = ParseOptions + +interface Options extends BuildOptions, LibsOptions { } +interface Config extends BuildConfig, LibsConfig { } + +let defaultGetTargetFileName = ( + (libName, _schemaName) => isKeyOf(libName, LIB_NAME_TO_TARGET_FILENAME) + ? `${LIB_NAME_TO_TARGET_FILENAME[libName]}.ts` as const + : `${libName}.ts` +) satisfies LibOptions['getTargetFileName'] + +let defaultPostProcessor = (content: string, schemaName: string) => fn.pipe( + content, + rewriteCoreInternalImport, + rewriteCoreNamespaceImport, + removeCoverageDirectives, + removeIgnoredImports, + rewriteSelfReferences(schemaName), +) + +let defaultLibOptions = { + relativePath: 'src/schemas', + excludeFiles: [], + getTargetFileName: defaultGetTargetFileName, +} satisfies LibOptions + +let defaultLibs = { + [Library.Core]: defaultLibOptions, + [Library.Equals]: defaultLibOptions, + [Library.ToJsonSchema]: defaultLibOptions, + [Library.ToString]: defaultLibOptions, + [Library.Validators]: defaultLibOptions, +} satisfies Record + +let defaultOptions = { + dryRun: false, + skipCleanup: false, + postProcessor: defaultPostProcessor, + excludeSchemas: null, + getExtensionFilesDir: () => { + if (!fs.existsSync(PATH.extensionsDir)) + throw Error('No extensions specified') + return PATH.extensionsDir + }, + getNamespaceFile: () => PATH.namespaceFile, + getSourceDir: () => { + if (fs.existsSync(PATH.sourceDir)) return PATH.sourceDir + else + try { fs.mkdirSync(PATH.sourceDir); return PATH.sourceDir } + catch (e) { throw Error('could not create source dir: ' + PATH.sourceDir) } + }, + getTempDir: () => { + if (fs.existsSync(PATH.tempDir)) return PATH.tempDir + else + try { fs.mkdirSync(PATH.tempDir); return PATH.tempDir } + catch (e) { throw Error('could not create temp dir: ' + PATH.tempDir) } + }, + getTargetDir: () => { + if (fs.existsSync(PATH.targetDir)) return PATH.targetDir + else + try { fs.mkdirSync(PATH.targetDir); return PATH.targetDir } + catch (e) { throw Error('could not create target dir: ' + PATH.targetDir) } + }, + libs: defaultLibs, +} satisfies Omit & LibsOptions, 'extensionFiles'> + +function parseLibOptions({ + excludeFiles = defaultLibOptions.excludeFiles, + relativePath = defaultLibOptions.relativePath, + getTargetFileName = defaultLibOptions.getTargetFileName, + includeFiles, +}: Partial): LibOptions { + return { + excludeFiles, + relativePath, + getTargetFileName, + ...includeFiles && { includeFiles } + } +} + +function parseOptions(options: Options): Config +function parseOptions({ + extensionFiles, + dryRun = defaultOptions.dryRun, + excludeSchemas = null, + getExtensionFilesDir = defaultOptions.getExtensionFilesDir, + getNamespaceFile = defaultOptions.getNamespaceFile, + getSourceDir = defaultOptions.getSourceDir, + getTargetDir = defaultOptions.getTargetDir, + getTempDir = defaultOptions.getTempDir, + libs, + postProcessor = defaultOptions.postProcessor, + skipCleanup = defaultOptions.skipCleanup, +}: Options): Config { + return { + dryRun, + excludeSchemas, + extensionFiles, + extensionFilesDir: getExtensionFilesDir(), + libs: fn.map(libs, parseLibOptions), + namespaceFile: getNamespaceFile(), + postProcessor, + skipCleanup, + sourceDir: getSourceDir(), + targetDir: getTargetDir(), + tempDir: getTempDir(), + } +} + +let tap + : (effect: (s: S) => T) => (x: S) => S + = (effect) => (x) => (effect(x), x) + +let ensureDir + : (dirpath: string, $: Config) => void + = (dirpath, $) => !$.dryRun + ? void (!fs.existsSync(dirpath) && fs.mkdirSync(dirpath)) + : void ( + console.group('[[DRY_RUN]]: `ensureDir`'), + console.debug('mkDir:', dirpath), + console.groupEnd() + ) + +function writeExtensionFiles($: Config) { + if (!fs.existsSync($.extensionFilesDir)) { + throw Error('Could not find extensions dir: ' + $.extensionFilesDir) + } + let extensionFiles = $.extensionFiles + // fs + // .readdirSync($.extensionFilesDir) + .filter(([filename]) => !EXTENSION_FILES_IGNORE_LIST.includes(filename)) + + extensionFiles.forEach(([filename, fileContent]) => { + let tempDirName = filename.slice(0, -'.ts'.length) + let tempDirPath = path.join($.tempDir, tempDirName) + let tempPath = path.join(tempDirPath, 'extension.ts') + // let sourcePath = path.join($.extensionFilesDir, filename) + // let content = fs.readFileSync(sourcePath).toString('utf8') + ensureDir(tempDirPath, $) + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `copyExtensionFiles`') + console.debug('\ntempPath:\n', tempPath) + console.debug('\nfile content:\n', fileContent) + console.groupEnd() + } else { + console.group('\n\n[[DRY_RUN]]:: `copyExtensionFiles`') + console.debug('\ntempPath:\n', tempPath) + console.debug('\nfile content:\n', fileContent) + console.groupEnd() + + fs.writeFileSync(tempPath, fileContent) + } + }) +} + +function buildSchemas($: Config): void { + let cache = new Set() + + return void fs.readdirSync( + path.join($.sourceDir), { withFileTypes: true }) + .filter(({ name }) => Object.keys($.libs).includes(name)) + .map( + (sourceDir) => { + let LIB_NAME = sourceDir.name + let LIB = $.libs[LIB_NAME] + return fn.pipe( + path.join( + sourceDir.parentPath, + LIB_NAME, + $.libs[LIB_NAME].relativePath, + ), + (schemasDir) => fs.readdirSync(schemasDir, { withFileTypes: true }), + fn.map( + (schemaFile) => { + let sourceFilePath = path.join(schemaFile.parentPath, schemaFile.name) + let sourceFileContent = fs.readFileSync(sourceFilePath).toString('utf8') + let targetFileName = LIB.getTargetFileName(LIB_NAME, schemaFile.name) + let schemaName = schemaFile.name.endsWith('.ts') + ? schemaFile.name.slice(0, -'.ts'.length) + : schemaFile.name + + let targetFilePath = path.join( + $.tempDir, + schemaName, + targetFileName + ) + + let tempDirPath = path.join( + $.tempDir, + schemaFile.name.slice(0, -'.ts'.length), + ) + + if (!cache.has(tempDirPath) && !$.dryRun) { + cache.add(tempDirPath) + ensureDir(tempDirPath, $) + } + + if (!$.dryRun) { + fs.writeFileSync( + targetFilePath, + sourceFileContent, + ) + } else { + console.group('\n\n[[DRY_RUN]]:: `buildSchemas`') + console.debug('\ntargetFilePath:\n', targetFilePath) + console.debug('\nsourceFileContent:\n', sourceFileContent) + console.groupEnd() + } + } + ), + ) + } + ) +} + +function getSourcePaths($: Config): Record> { + if (!fs.existsSync($.tempDir)) { + throw Error('[getSourcePaths] Expected temp directory to exist: ' + $.tempDir) + } + + return fs.readdirSync($.tempDir, { withFileTypes: true }) + .reduce( + (acc, { name, parentPath }) => ({ + ...acc, + [name]: fs + .readdirSync(path.join(parentPath, name), { withFileTypes: true }) + .reduce( + (acc, { name, parentPath }) => ({ + ...acc, + [name.slice(0, -'.ts'.length)]: path.join(parentPath, name) + }), + {} + ) + }), + {} + ) +} + +function createTargetPaths($: Config, sourcePaths: Record>) { + return fn.map(sourcePaths, (_, schemaName) => path.join($.targetDir, `${schemaName}.ts`)) +} + +export function writeSchemas($: Config, sources: Record>, targets: Record): void { + let schemas = generateSchemas(sources, targets) + for (let [target, generatedContent] of schemas) { + let pathSegments = target.split('/') + let fileName = pathSegments[pathSegments.length - 1] + let schemaName = fileName.endsWith('.ts') ? fileName.slice(0, -'.ts'.length) : fileName + let content = $.postProcessor(generatedContent, schemaName) + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `writeSchemas`') + console.debug('\ntarget:\n', target) + console.debug('\ncontent after post-processing:\n', content) + console.groupEnd() + } else { + fs.writeFileSync(target, content) + } + } +} + +function getNamespaceFileContent(previousContent: string, $: Config, sources: Record>) { + let targetDirNames = $.targetDir.split('/') + let targetDirName = targetDirNames[targetDirNames.length - 1] + let lines = Object.keys(sources).map((schemaName) => `export { ${schemaName} } from './${targetDirName}/${schemaName}.js'`) + return previousContent + '\r\n' + lines.join('\n') + '\r\n' +} + +export function writeNamespaceFile($: Config, sources: Record>) { + let content = getNamespaceFileContent(fs.readFileSync($.namespaceFile).toString('utf8'), $, sources) + if (content.includes('export {')) { + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `writeNamespaceFile`') + console.debug('\ntarget file already have term-level exports:\n', content) + console.groupEnd() + } else { + return void 0 + } + } + else if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `writeNamespaceFile`') + console.debug('\nnamespace file path:\n', $.namespaceFile) + console.debug('\nnamespace file content:\n', content) + console.groupEnd() + } else { + fs.writeFileSync($.namespaceFile, content) + } +} + +export function cleanupTempDir($: Config) { + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `cleanupTempDir`') + console.debug('\ntemp dir path:\n', $.tempDir) + console.groupEnd() + } + else { + void fs.rmSync($.tempDir, { force: true, recursive: true }) + } +} + +function build(options: Options) { + options.dryRun + let $ = parseOptions(options) + let extensionFiles = $.extensionFiles + + void ensureDir($.tempDir, $) + void writeExtensionFiles($) + buildSchemas($) + + let sources = fn.pipe( + getSourcePaths($), + ) + let targets = createTargetPaths($, sources) + + if ($.dryRun) { + console.group('\n\n[[DRY_RUN]]:: `build`') + console.debug('\nsources:\n', sources) + console.debug('\ntargets:\n', targets) + console.groupEnd() + } + + fn.map(sources, (v, k) => { + console.log('source k:', k, 'typeof k:', typeof k) + console.log('source v:', v) + }) + + void ensureDir($.targetDir, $) + void writeSchemas($, sources, targets) + void writeNamespaceFile($, sources) + + if ($.skipCleanup) { + console.group('\n\n[[SKIP_CLEANUP]]: `build`\n') + console.debug('\n`build` received \'skipCleanup\': true. ' + $.tempDir + ' was not removed.') + console.groupEnd() + return void 0 + } else { + void cleanupTempDir($) + } +} + +let extensionFiles = fs + .readdirSync(PATH.extensionsDir, { withFileTypes: true }) + .map( + ({ name, parentPath }) => + fn.pipe( + fs.readFileSync(path.join(parentPath, name)), + (buffer) => buffer.toString('utf8'), + (content) => [name, content] satisfies [any, any] + ) + ) + +console.log('extensionFiles', extensionFiles) + +build({ + ...defaultOptions, + // dryRun: true, + skipCleanup: true, + extensionFiles, +}) diff --git a/packages/schema-generator/src/extensions/any.ts b/packages/schema-generator/src/extensions/any.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/src/extensions/any.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/extensions/array.ts b/packages/schema-generator/src/extensions/array.ts new file mode 100644 index 00000000..0927d4dd --- /dev/null +++ b/packages/schema-generator/src/extensions/array.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toJsonSchema, + validate, + toString, + equals, +} diff --git a/packages/schema-generator/src/extensions/bigint.ts b/packages/schema-generator/src/extensions/bigint.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/extensions/bigint.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/extensions/boolean.ts b/packages/schema-generator/src/extensions/boolean.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/extensions/boolean.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/extensions/eq.ts b/packages/schema-generator/src/extensions/eq.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/extensions/eq.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/extensions/equals.ts b/packages/schema-generator/src/extensions/equals.ts new file mode 100644 index 00000000..013153b1 --- /dev/null +++ b/packages/schema-generator/src/extensions/equals.ts @@ -0,0 +1,3 @@ +export { dummyEquals as equals } +let dummyEquals = (..._: any) => { throw Error('Called dummy equals') } +interface dummyEquals<_ = any> { } diff --git a/packages/schema-generator/src/extensions/integer.ts b/packages/schema-generator/src/extensions/integer.ts new file mode 100644 index 00000000..1f68a7e8 --- /dev/null +++ b/packages/schema-generator/src/extensions/integer.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toString, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/extensions/intersect.ts b/packages/schema-generator/src/extensions/intersect.ts new file mode 100644 index 00000000..0927d4dd --- /dev/null +++ b/packages/schema-generator/src/extensions/intersect.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toJsonSchema, + validate, + toString, + equals, +} diff --git a/packages/schema-generator/src/extensions/never.ts b/packages/schema-generator/src/extensions/never.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/extensions/never.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/extensions/null.ts b/packages/schema-generator/src/extensions/null.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/extensions/null.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/extensions/number.ts b/packages/schema-generator/src/extensions/number.ts new file mode 100644 index 00000000..1a06ace0 --- /dev/null +++ b/packages/schema-generator/src/extensions/number.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let Definitions = { + toString, + equals, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/extensions/object.ts b/packages/schema-generator/src/extensions/object.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/extensions/object.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/extensions/of.ts b/packages/schema-generator/src/extensions/of.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/extensions/of.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/extensions/optional.ts b/packages/schema-generator/src/extensions/optional.ts new file mode 100644 index 00000000..0e7c4478 --- /dev/null +++ b/packages/schema-generator/src/extensions/optional.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} + diff --git a/packages/schema-generator/src/extensions/record.ts b/packages/schema-generator/src/extensions/record.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/extensions/record.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/extensions/string.ts b/packages/schema-generator/src/extensions/string.ts new file mode 100644 index 00000000..c64c1266 --- /dev/null +++ b/packages/schema-generator/src/extensions/string.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + toString, + equals, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/extensions/symbol.ts b/packages/schema-generator/src/extensions/symbol.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/extensions/symbol.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/extensions/toJsonSchema.ts b/packages/schema-generator/src/extensions/toJsonSchema.ts new file mode 100644 index 00000000..16341d68 --- /dev/null +++ b/packages/schema-generator/src/extensions/toJsonSchema.ts @@ -0,0 +1,3 @@ +export { dummyToJsonSchema as toJsonSchema } +let dummyToJsonSchema = (..._: any) => { throw Error('Called dummy toJsonSchema') } +interface dummyToJsonSchema<_ = any> { } diff --git a/packages/schema-generator/src/extensions/toString.ts b/packages/schema-generator/src/extensions/toString.ts new file mode 100644 index 00000000..d7215f1f --- /dev/null +++ b/packages/schema-generator/src/extensions/toString.ts @@ -0,0 +1,3 @@ +export { dummyToString as toString } +let dummyToString = (..._: any) => { throw Error('Called dummy toString') } +interface dummyToString<_ = any> { } diff --git a/packages/schema-generator/src/extensions/tuple.ts b/packages/schema-generator/src/extensions/tuple.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/extensions/tuple.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/extensions/undefined.ts b/packages/schema-generator/src/extensions/undefined.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/extensions/undefined.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/extensions/union.ts b/packages/schema-generator/src/extensions/union.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/extensions/union.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/extensions/unknown.ts b/packages/schema-generator/src/extensions/unknown.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/extensions/unknown.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/extensions/validate.ts b/packages/schema-generator/src/extensions/validate.ts new file mode 100644 index 00000000..ba09b9fe --- /dev/null +++ b/packages/schema-generator/src/extensions/validate.ts @@ -0,0 +1,3 @@ +export { dummyValidate as validate } +let dummyValidate = (..._: any) => { throw Error('Called dummy validate') } +interface dummyValidate<_ = any> { } diff --git a/packages/schema-generator/src/extensions/void.ts b/packages/schema-generator/src/extensions/void.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/extensions/void.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/any/core.ts b/packages/schema-generator/src/temp/any/core.ts new file mode 100644 index 00000000..877f92af --- /dev/null +++ b/packages/schema-generator/src/temp/any/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { any_ as any } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface any_ extends any_.core { + //<%= Types %> +} + +function AnySchema(src: unknown): src is any { return true } +AnySchema.tag = URI.any +AnySchema.def = void 0 as any + +const any_ = Object_assign( + AnySchema, + userDefinitions, +) as any_ + +Object_assign(any_, userExtensions) + +declare namespace any_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.any + _type: any + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/temp/any/equals.ts b/packages/schema-generator/src/temp/any/equals.ts new file mode 100644 index 00000000..09d8da5f --- /dev/null +++ b/packages/schema-generator/src/temp/any/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from "@traversable/registry" +import { Object_is } from "@traversable/registry" + +export type equals = Equal +export function equals(left: unknown, right: unknown): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/src/temp/any/extension.ts b/packages/schema-generator/src/temp/any/extension.ts new file mode 100644 index 00000000..c4ecbb88 --- /dev/null +++ b/packages/schema-generator/src/temp/any/extension.ts @@ -0,0 +1,21 @@ +import { toJsonSchema } from './toJsonSchema.js' +import { validate } from './validate.js' +import { toString } from './toString.js' +import { equals } from './equals.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/any/toJsonSchema.ts b/packages/schema-generator/src/temp/any/toJsonSchema.ts new file mode 100644 index 00000000..25336fc6 --- /dev/null +++ b/packages/schema-generator/src/temp/any/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return unknownToJsonSchema +} diff --git a/packages/schema-generator/src/temp/any/toString.ts b/packages/schema-generator/src/temp/any/toString.ts new file mode 100644 index 00000000..f70aa050 --- /dev/null +++ b/packages/schema-generator/src/temp/any/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'any' } +export function toString(): 'any' { return 'any' } diff --git a/packages/schema-generator/src/temp/any/validate.ts b/packages/schema-generator/src/temp/any/validate.ts new file mode 100644 index 00000000..d286c4b5 --- /dev/null +++ b/packages/schema-generator/src/temp/any/validate.ts @@ -0,0 +1,10 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.any): validate { + validateAny.tag = URI.any + function validateAny() { return true as const } + return validateAny +} diff --git a/packages/schema-generator/src/temp/array/core.ts b/packages/schema-generator/src/temp/array/core.ts new file mode 100644 index 00000000..65c8c03c --- /dev/null +++ b/packages/schema-generator/src/temp/array/core.ts @@ -0,0 +1,128 @@ +import type { + Bounds, + Integer, + Unknown, +} from '@traversable/registry' +import { + Array_isArray, + array as arrayOf, + bindUserExtensions, + carryover, + within, + _isPredicate, + has, + Math_max, + Math_min, + Number_isSafeInteger, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Guarded, Schema, SchemaLike } from '../namespace.js' + +import type { of } from './of.js' + +/** @internal */ +function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { + return Object_assign(function BoundedArraySchema(u: unknown) { + return Array_isArray(u) && within(bounds)(u.length) + }, carry, array(schema)) +} + +export interface array extends array.core { + //<%= Types %> +} + +export function array(schema: S, readonly: 'readonly'): readonlyArray +export function array(schema: S): array +export function array(schema: S): array>> +export function array(schema: S): array { + return array.def(schema) +} + +export namespace array { + export let userDefinitions: Record = { + //<%= Definitions %> + } as array + export function def(x: S, prev?: array): array + export function def(x: S, prev?: unknown): array + export function def(x: S, prev?: array): array + /* v8 ignore next 1 */ + export function def(x: unknown, prev?: unknown): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray + function ArraySchema(src: unknown) { return predicate(src) } + ArraySchema.tag = URI.array + ArraySchema.def = x + ArraySchema.min = function arrayMin(minLength: Min) { + return Object_assign( + boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), + { minLength }, + ) + } + ArraySchema.max = function arrayMax(maxLength: Max) { + return Object_assign( + boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), + { maxLength }, + ) + } + ArraySchema.between = function arrayBetween( + min: Min, + max: Max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max) + ) { + return Object_assign( + boundedArray(x, { gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) + } + if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength + if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength + Object_assign(ArraySchema, userDefinitions) + return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) + } +} + +export declare namespace array { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.array + get def(): S + _type: S['_type' & keyof S][] + minLength?: number + maxLength?: number + min>(minLength: Min): array.Min + max>(maxLength: Max): array.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> + } + type Min + = [Self] extends [{ maxLength: number }] + ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> + : array.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> + : array.max + ; + interface min extends array { minLength: Min } + interface max extends array { maxLength: Max } + interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } + type type = never | T +} + +export const readonlyArray: { + (schema: S): readonlyArray + (schema: S): readonlyArray> +} = array +export interface readonlyArray { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: ReadonlyArray +} diff --git a/packages/schema-generator/src/temp/array/equals.ts b/packages/schema-generator/src/temp/array/equals.ts new file mode 100644 index 00000000..aa06a7bf --- /dev/null +++ b/packages/schema-generator/src/temp/array/equals.ts @@ -0,0 +1,24 @@ +import type { Equal } from '@traversable/registry' +import { has, Array_isArray, Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal + +export function equals(arraySchema: t.array): equals +export function equals(arraySchema: t.array): equals +export function equals({ def }: t.array<{ equals: Equal }>): Equal { + let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is + function arrayEquals(l: unknown[], r: unknown[]): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + let len = l.length + if (len !== r.length) return false + for (let ix = len; ix-- !== 0;) + if (!equals(l[ix], r[ix])) return false + return true + } else return false + } + return arrayEquals +} + diff --git a/packages/schema-generator/src/temp/array/extension.ts b/packages/schema-generator/src/temp/array/extension.ts new file mode 100644 index 00000000..0927d4dd --- /dev/null +++ b/packages/schema-generator/src/temp/array/extension.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toJsonSchema, + validate, + toString, + equals, +} diff --git a/packages/schema-generator/src/temp/array/toJsonSchema.ts b/packages/schema-generator/src/temp/array/toJsonSchema.ts new file mode 100644 index 00000000..cbdda659 --- /dev/null +++ b/packages/schema-generator/src/temp/array/toJsonSchema.ts @@ -0,0 +1,36 @@ +import type { t } from '@traversable/schema-core' +import type * as T from '@traversable/registry' +import type { SizeBounds } from '@traversable/schema-to-json-schema' +import { hasSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): never | T.Force< + & { type: 'array', items: T.Returns } + & T.PickIfDefined + > +} + +export function toJsonSchema>(arraySchema: T): toJsonSchema +export function toJsonSchema(arraySchema: T): toJsonSchema +export function toJsonSchema( + { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, +): () => { + type: 'array' + items: unknown + minLength?: number + maxLength?: number +} { + function arrayToJsonSchema() { + let items = hasSchema(def) ? def.toJsonSchema() : def + let out = { + type: 'array' as const, + items, + minLength, + maxLength, + } + if (typeof minLength !== 'number') delete out.minLength + if (typeof maxLength !== 'number') delete out.maxLength + return out + } + return arrayToJsonSchema +} diff --git a/packages/schema-generator/src/temp/array/toString.ts b/packages/schema-generator/src/temp/array/toString.ts new file mode 100644 index 00000000..5114ef0b --- /dev/null +++ b/packages/schema-generator/src/temp/array/toString.ts @@ -0,0 +1,22 @@ +import type { t } from '@traversable/schema-core' + +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType})[]` +} + +export function toString(arraySchema: t.array): toString +export function toString(arraySchema: t.array): toString +export function toString({ def }: { def: unknown }) { + function arrayToString() { + let body = ( + !!def + && typeof def === 'object' + && 'toString' in def + && typeof def.toString === 'function' + ) ? def.toString() + : '${string}' + return ('(' + body + ')[]') + } + return arrayToString +} diff --git a/packages/schema-generator/src/temp/array/validate.ts b/packages/schema-generator/src/temp/array/validate.ts new file mode 100644 index 00000000..4d8e7958 --- /dev/null +++ b/packages/schema-generator/src/temp/array/validate.ts @@ -0,0 +1,27 @@ +import { URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { Errors, NullaryErrors } from '@traversable/derive-validators' + +export type validate = never | ValidationFn +export function validate(arraySchema: t.array): validate +export function validate(arraySchema: t.array): validate +export function validate( + { def: { validate = () => true }, minLength, maxLength }: t.array +) { + validateArray.tag = URI.array + function validateArray(u: unknown, path = Array.of()) { + if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] + let errors = Array.of() + if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) + if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) + for (let i = 0, len = u.length; i < len; i++) { + let y = u[i] + let results = validate(y, [...path, i]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateArray +} diff --git a/packages/schema-generator/src/temp/bigint/core.ts b/packages/schema-generator/src/temp/bigint/core.ts new file mode 100644 index 00000000..f7d55261 --- /dev/null +++ b/packages/schema-generator/src/temp/bigint/core.ts @@ -0,0 +1,99 @@ +import type { Bounds, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Object_assign, + URI, + withinBig as within, +} from '@traversable/registry' + +export { bigint_ as bigint } + +/** @internal */ +function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedBigIntSchema(u: unknown) { + return bigint_(u) && within(bounds)(u) + }, carry, bigint_) +} + +interface bigint_ extends bigint_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function BigIntSchema(src: unknown) { return typeof src === 'bigint' } +BigIntSchema.tag = URI.bigint +BigIntSchema.def = 0n + +const bigint_ = Object_assign( + BigIntSchema, + userDefinitions, +) as bigint_ + +bigint_.min = function bigIntMin(minimum) { + return Object_assign( + boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +bigint_.max = function bigIntMax(maximum) { + return Object_assign( + boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +bigint_.between = function bigIntBetween( + min, + max, + minimum = (max < min ? max : min), + maximum = (max < min ? min : max), +) { + return Object_assign( + boundedBigInt({ gte: minimum, lte: maximum }), + { minimum, maximum } + ) +} + +Object_assign( + bigint_, + bindUserExtensions(bigint_, userExtensions), +) + +declare namespace bigint_ { + interface core extends bigint_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: bigint + tag: URI.bigint + get def(): this['_type'] + minimum?: bigint + maximum?: bigint + } + type Min + = [Self] extends [{ maximum: bigint }] + ? bigint_.between<[min: X, max: Self['maximum']]> + : bigint_.min + ; + type Max + = [Self] extends [{ minimum: bigint }] + ? bigint_.between<[min: Self['minimum'], max: X]> + : bigint_.max + ; + interface methods { + min(minimum: Min): bigint_.Min + max(maximum: Max): bigint_.Max + between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> + } + interface min extends bigint_ { minimum: Min } + interface max extends bigint_ { maximum: Max } + interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } +} + diff --git a/packages/schema-generator/src/temp/bigint/equals.ts b/packages/schema-generator/src/temp/bigint/equals.ts new file mode 100644 index 00000000..7c98bbf1 --- /dev/null +++ b/packages/schema-generator/src/temp/bigint/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' + +export type equals = Equal +export function equals(left: bigint, right: bigint): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/src/temp/bigint/extension.ts b/packages/schema-generator/src/temp/bigint/extension.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/temp/bigint/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/bigint/toJsonSchema.ts b/packages/schema-generator/src/temp/bigint/toJsonSchema.ts new file mode 100644 index 00000000..f6c7bc5b --- /dev/null +++ b/packages/schema-generator/src/temp/bigint/toJsonSchema.ts @@ -0,0 +1,7 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function bigintToJsonSchema(): void { + return void 0 + } + return bigintToJsonSchema +} diff --git a/packages/schema-generator/src/temp/bigint/toString.ts b/packages/schema-generator/src/temp/bigint/toString.ts new file mode 100644 index 00000000..793c903e --- /dev/null +++ b/packages/schema-generator/src/temp/bigint/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'bigint' } +export function toString(): 'bigint' { return 'bigint' } diff --git a/packages/schema-generator/src/temp/bigint/validate.ts b/packages/schema-generator/src/temp/bigint/validate.ts new file mode 100644 index 00000000..7e3284da --- /dev/null +++ b/packages/schema-generator/src/temp/bigint/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(bigIntSchema: S): validate { + validateBigInt.tag = URI.bigint + function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { + return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] + } + return validateBigInt +} diff --git a/packages/schema-generator/src/temp/boolean/core.ts b/packages/schema-generator/src/temp/boolean/core.ts new file mode 100644 index 00000000..c89adf4b --- /dev/null +++ b/packages/schema-generator/src/temp/boolean/core.ts @@ -0,0 +1,37 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { boolean_ as boolean } + +interface boolean_ extends boolean_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } + +BooleanSchema.tag = URI.boolean +BooleanSchema.def = false + +const boolean_ = Object_assign( + BooleanSchema, + userDefinitions, +) as boolean_ + +Object_assign(boolean_, userExtensions) + +declare namespace boolean_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.boolean + _type: boolean + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/temp/boolean/equals.ts b/packages/schema-generator/src/temp/boolean/equals.ts new file mode 100644 index 00000000..306bb12b --- /dev/null +++ b/packages/schema-generator/src/temp/boolean/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' + +export type equals = Equal +export function equals(left: boolean, right: boolean): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/src/temp/boolean/extension.ts b/packages/schema-generator/src/temp/boolean/extension.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/temp/boolean/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/boolean/toJsonSchema.ts b/packages/schema-generator/src/temp/boolean/toJsonSchema.ts new file mode 100644 index 00000000..d1b86f70 --- /dev/null +++ b/packages/schema-generator/src/temp/boolean/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'boolean' } } +export function toJsonSchema(): toJsonSchema { + function booleanToJsonSchema() { return { type: 'boolean' as const } } + return booleanToJsonSchema +} diff --git a/packages/schema-generator/src/temp/boolean/toString.ts b/packages/schema-generator/src/temp/boolean/toString.ts new file mode 100644 index 00000000..3c408e57 --- /dev/null +++ b/packages/schema-generator/src/temp/boolean/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'boolean' } +export function toString(): 'boolean' { return 'boolean' } diff --git a/packages/schema-generator/src/temp/boolean/validate.ts b/packages/schema-generator/src/temp/boolean/validate.ts new file mode 100644 index 00000000..76cee261 --- /dev/null +++ b/packages/schema-generator/src/temp/boolean/validate.ts @@ -0,0 +1,12 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(booleanSchema: t.boolean): validate { + validateBoolean.tag = URI.boolean + function validateBoolean(u: unknown, path = Array.of()) { + return booleanSchema(true as const) || [NullaryErrors.null(u, path)] + } + return validateBoolean +} diff --git a/packages/schema-generator/src/temp/eq/core.ts b/packages/schema-generator/src/temp/eq/core.ts new file mode 100644 index 00000000..d701a0f6 --- /dev/null +++ b/packages/schema-generator/src/temp/eq/core.ts @@ -0,0 +1,41 @@ +import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' +import { applyOptions, bindUserExtensions, _isPredicate, Object_assign, URI } from '@traversable/registry' + +export function eq>(value: V, options?: Options): eq> +export function eq(value: V, options?: Options): eq +export function eq(value: V, options?: Options): eq { + return eq.def(value, options) +} + +export interface eq extends eq.core { + //<%= Types %> +} + +export namespace eq { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(value: T, options?: Options): eq + /* v8 ignore next 1 */ + export function def(x: T, $?: Options): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const options = applyOptions($) + const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return predicate(src) } + EqSchema.tag = URI.eq + EqSchema.def = x + Object_assign(EqSchema, eq.userDefinitions) + return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) + } +} + +export declare namespace eq { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.eq + _type: V + get def(): V + } +} diff --git a/packages/schema-generator/src/temp/eq/equals.ts b/packages/schema-generator/src/temp/eq/equals.ts new file mode 100644 index 00000000..774127f6 --- /dev/null +++ b/packages/schema-generator/src/temp/eq/equals.ts @@ -0,0 +1,11 @@ +import type { Equal } from '@traversable/registry' +import { laxEquals } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal +export function equals(eqSchema: t.eq): equals +export function equals(): Equal { + return function eqEquals(left: any, right: any) { + return laxEquals(left, right) + } +} diff --git a/packages/schema-generator/src/temp/eq/extension.ts b/packages/schema-generator/src/temp/eq/extension.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/temp/eq/extension.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/temp/eq/toJsonSchema.ts b/packages/schema-generator/src/temp/eq/toJsonSchema.ts new file mode 100644 index 00000000..8edfd03a --- /dev/null +++ b/packages/schema-generator/src/temp/eq/toJsonSchema.ts @@ -0,0 +1,8 @@ +import type { t } from '@traversable/schema-core' + +export interface toJsonSchema { (): { const: T } } +export function toJsonSchema(eqSchema: t.eq): toJsonSchema +export function toJsonSchema({ def }: t.eq) { + function eqToJsonSchema() { return { const: def } } + return eqToJsonSchema +} diff --git a/packages/schema-generator/src/temp/eq/toString.ts b/packages/schema-generator/src/temp/eq/toString.ts new file mode 100644 index 00000000..3c224bea --- /dev/null +++ b/packages/schema-generator/src/temp/eq/toString.ts @@ -0,0 +1,17 @@ +import type { Key } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { stringify } from '@traversable/schema-to-string' + +export interface toString { + (): [Key] extends [never] + ? [T] extends [symbol] ? 'symbol' : 'symbol' + : [T] extends [string] ? `'${T}'` : Key +} + +export function toString(eqSchema: t.eq): toString +export function toString({ def }: t.eq): () => string { + function eqToString(): string { + return typeof def === 'symbol' ? 'symbol' : stringify(def) + } + return eqToString +} diff --git a/packages/schema-generator/src/temp/eq/validate.ts b/packages/schema-generator/src/temp/eq/validate.ts new file mode 100644 index 00000000..2f02ba7d --- /dev/null +++ b/packages/schema-generator/src/temp/eq/validate.ts @@ -0,0 +1,17 @@ +import { Equal, getConfig, URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { Validate } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + +export type validate = Validate +export function validate(eqSchema: t.eq): validate +export function validate({ def }: t.eq): validate { + validateEq.tag = URI.eq + function validateEq(u: unknown, path = Array.of()) { + let options = getConfig().schema + let equals = options?.eq?.equalsFn || Equal.lax + if (equals(def, u)) return true + else return [Errors.eq(u, path, def)] + } + return validateEq +} diff --git a/packages/schema-generator/src/temp/integer/core.ts b/packages/schema-generator/src/temp/integer/core.ts new file mode 100644 index 00000000..2969e5a1 --- /dev/null +++ b/packages/schema-generator/src/temp/integer/core.ts @@ -0,0 +1,100 @@ +import type { Bounds, Integer, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Number_isSafeInteger, + Object_assign, + URI, + within, +} from '@traversable/registry' + + +export { integer } + +/** @internal */ +function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedIntegerSchema(u: unknown) { + return integer(u) && within(bounds)(u) + }, carry, integer) +} + +interface integer extends integer.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } +IntegerSchema.tag = URI.integer +IntegerSchema.def = 0 + +const integer = Object_assign( + IntegerSchema, + userDefinitions, +) as integer + +integer.min = function integerMin(minimum) { + return Object_assign( + boundedInteger({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +integer.max = function integerMax(maximum) { + return Object_assign( + boundedInteger({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +integer.between = function integerBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedInteger({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + integer, + bindUserExtensions(integer, userExtensions), +) + +declare namespace integer { + interface core extends integer.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.integer + get def(): this['_type'] + minimum?: number + maximum?: number + } + interface methods { + min>(minimum: Min): integer.Min + max>(maximum: Max): integer.Max + between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maximum: number }] + ? integer.between<[min: X, max: Self['maximum']]> + : integer.min + type Max + = [Self] extends [{ minimum: number }] + ? integer.between<[min: Self['minimum'], max: X]> + : integer.max + interface min extends integer { minimum: Min } + interface max extends integer { maximum: Max } + interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } +} diff --git a/packages/schema-generator/src/temp/integer/equals.ts b/packages/schema-generator/src/temp/integer/equals.ts new file mode 100644 index 00000000..29fcd602 --- /dev/null +++ b/packages/schema-generator/src/temp/integer/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { SameValueNumber } from '@traversable/registry' + +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} diff --git a/packages/schema-generator/src/temp/integer/extension.ts b/packages/schema-generator/src/temp/integer/extension.ts new file mode 100644 index 00000000..1f68a7e8 --- /dev/null +++ b/packages/schema-generator/src/temp/integer/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toString, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/temp/integer/toJsonSchema.ts b/packages/schema-generator/src/temp/integer/toJsonSchema.ts new file mode 100644 index 00000000..d531e292 --- /dev/null +++ b/packages/schema-generator/src/temp/integer/toJsonSchema.ts @@ -0,0 +1,23 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.integer): toJsonSchema { + function integerToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'integer' as const, + ...bounds, + } + } + return integerToJsonSchema +} diff --git a/packages/schema-generator/src/temp/integer/toString.ts b/packages/schema-generator/src/temp/integer/toString.ts new file mode 100644 index 00000000..912565e6 --- /dev/null +++ b/packages/schema-generator/src/temp/integer/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } diff --git a/packages/schema-generator/src/temp/integer/validate.ts b/packages/schema-generator/src/temp/integer/validate.ts new file mode 100644 index 00000000..5f044c5d --- /dev/null +++ b/packages/schema-generator/src/temp/integer/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(integerSchema: S): validate { + validateInteger.tag = URI.integer + function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { + return integerSchema(u) || [NullaryErrors.integer(u, path)] + } + return validateInteger +} diff --git a/packages/schema-generator/src/temp/intersect/core.ts b/packages/schema-generator/src/temp/intersect/core.ts new file mode 100644 index 00000000..a3f89c92 --- /dev/null +++ b/packages/schema-generator/src/temp/intersect/core.ts @@ -0,0 +1,50 @@ +import type { Unknown } from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + intersect as intersect$, + isUnknown as isAny, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Entry, IntersectType, Schema, SchemaLike } from '../namespace.js' + +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: readonly unknown[]) { + return intersect.def(schemas) +} + +export interface intersect extends intersect.core { + //<%= Types %> +} + +export namespace intersect { + export let userDefinitions: Record = { + //<%= Definitions %> + } as intersect + export function def(xs: readonly [...T]): intersect + /* v8 ignore next 1 */ + export function def(xs: readonly unknown[]): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny + function IntersectSchema(src: unknown) { return predicate(src) } + IntersectSchema.tag = URI.intersect + IntersectSchema.def = xs + Object_assign(IntersectSchema, intersect.userDefinitions) + return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) + } +} + +export declare namespace intersect { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.intersect + get def(): S + _type: IntersectType + } + type type> = never | T +} diff --git a/packages/schema-generator/src/temp/intersect/equals.ts b/packages/schema-generator/src/temp/intersect/equals.ts new file mode 100644 index 00000000..ce880aa1 --- /dev/null +++ b/packages/schema-generator/src/temp/intersect/equals.ts @@ -0,0 +1,16 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = Equal +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals(intersectSchema: t.intersect<[...S]>): equals +export function equals({ def }: t.intersect<{ equals: Equal }[]>): Equal { + function intersectEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (!def[ix].equals(l, r)) return false + return true + } + return intersectEquals +} diff --git a/packages/schema-generator/src/temp/intersect/extension.ts b/packages/schema-generator/src/temp/intersect/extension.ts new file mode 100644 index 00000000..0927d4dd --- /dev/null +++ b/packages/schema-generator/src/temp/intersect/extension.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toJsonSchema, + validate, + toString, + equals, +} diff --git a/packages/schema-generator/src/temp/intersect/toJsonSchema.ts b/packages/schema-generator/src/temp/intersect/toJsonSchema.ts new file mode 100644 index 00000000..d3942a94 --- /dev/null +++ b/packages/schema-generator/src/temp/intersect/toJsonSchema.ts @@ -0,0 +1,20 @@ +import type { Returns } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + allOf: { [I in keyof T]: Returns } + } +} + +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema +export function toJsonSchema({ def }: t.intersect): () => {} { + function intersectToJsonSchema() { + return { + allOf: def.map(getSchema) + } + } + return intersectToJsonSchema +} diff --git a/packages/schema-generator/src/temp/intersect/toString.ts b/packages/schema-generator/src/temp/intersect/toString.ts new file mode 100644 index 00000000..2e179159 --- /dev/null +++ b/packages/schema-generator/src/temp/intersect/toString.ts @@ -0,0 +1,18 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + (): never | [T] extends [readonly []] ? 'unknown' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` +} + +export function toString(intersectSchema: t.intersect): toString +export function toString({ def }: t.intersect): () => string { + function intersectToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' + } + return intersectToString +} diff --git a/packages/schema-generator/src/temp/intersect/validate.ts b/packages/schema-generator/src/temp/intersect/validate.ts new file mode 100644 index 00000000..8a586df2 --- /dev/null +++ b/packages/schema-generator/src/temp/intersect/validate.ts @@ -0,0 +1,21 @@ +import { URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(intersectSchema: t.intersect): validate +export function validate(intersectSchema: t.intersect): validate +export function validate({ def }: t.intersect) { + validateIntersect.tag = URI.intersect + function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results !== true) + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + return errors.length === 0 || errors + } + return validateIntersect +} diff --git a/packages/schema-generator/src/temp/never/core.ts b/packages/schema-generator/src/temp/never/core.ts new file mode 100644 index 00000000..a0077281 --- /dev/null +++ b/packages/schema-generator/src/temp/never/core.ts @@ -0,0 +1,37 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { never_ as never } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface never_ extends never_.core { + //<%= Types %> +} + +function NeverSchema(src: unknown): src is never { return false } +NeverSchema.tag = URI.never; +NeverSchema.def = void 0 as never + +const never_ = Object_assign( + NeverSchema, + userDefinitions, +) as never_ + +Object_assign(never_, userExtensions) + +export declare namespace never_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.never + _type: never + get def(): this['_type'] + } +} + diff --git a/packages/schema-generator/src/temp/never/equals.ts b/packages/schema-generator/src/temp/never/equals.ts new file mode 100644 index 00000000..3ed89421 --- /dev/null +++ b/packages/schema-generator/src/temp/never/equals.ts @@ -0,0 +1,6 @@ +import type { Equal } from '@traversable/registry' + +export type equals = Equal +export function equals(left: never, right: never): boolean { + return false +} diff --git a/packages/schema-generator/src/temp/never/extension.ts b/packages/schema-generator/src/temp/never/extension.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/temp/never/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/never/toJsonSchema.ts b/packages/schema-generator/src/temp/never/toJsonSchema.ts new file mode 100644 index 00000000..d22338df --- /dev/null +++ b/packages/schema-generator/src/temp/never/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): never } +export function toJsonSchema(): toJsonSchema { + function neverToJsonSchema() { return void 0 as never } + return neverToJsonSchema +} diff --git a/packages/schema-generator/src/temp/never/toString.ts b/packages/schema-generator/src/temp/never/toString.ts new file mode 100644 index 00000000..aaabf80d --- /dev/null +++ b/packages/schema-generator/src/temp/never/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'never' } +export function toString(): 'never' { return 'never' } diff --git a/packages/schema-generator/src/temp/never/validate.ts b/packages/schema-generator/src/temp/never/validate.ts new file mode 100644 index 00000000..52ce9ee5 --- /dev/null +++ b/packages/schema-generator/src/temp/never/validate.ts @@ -0,0 +1,10 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.never): validate { + validateNever.tag = URI.never + function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } + return validateNever +} diff --git a/packages/schema-generator/src/temp/null/core.ts b/packages/schema-generator/src/temp/null/core.ts new file mode 100644 index 00000000..befebeb5 --- /dev/null +++ b/packages/schema-generator/src/temp/null/core.ts @@ -0,0 +1,39 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { null_ as null, null_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface null_ extends null_.core { + //<%= Types %> +} + +function NullSchema(src: unknown): src is null { return src === null } +NullSchema.def = null +NullSchema.tag = URI.null + +const null_ = Object_assign( + NullSchema, + userDefinitions, +) as null_ + +Object_assign( + null_, + userExtensions, +) + +declare namespace null_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.null + _type: null + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/temp/null/equals.ts b/packages/schema-generator/src/temp/null/equals.ts new file mode 100644 index 00000000..12c2f636 --- /dev/null +++ b/packages/schema-generator/src/temp/null/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' + +export type equals = Equal +export function equals(left: null, right: null): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/src/temp/null/extension.ts b/packages/schema-generator/src/temp/null/extension.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/temp/null/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/null/toJsonSchema.ts b/packages/schema-generator/src/temp/null/toJsonSchema.ts new file mode 100644 index 00000000..7a3b7c3a --- /dev/null +++ b/packages/schema-generator/src/temp/null/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'null', enum: [null] } } +export function toJsonSchema(): toJsonSchema { + function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } + return nullToJsonSchema +} diff --git a/packages/schema-generator/src/temp/null/toString.ts b/packages/schema-generator/src/temp/null/toString.ts new file mode 100644 index 00000000..35c3aef8 --- /dev/null +++ b/packages/schema-generator/src/temp/null/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'null' } +export function toString(): 'null' { return 'null' } diff --git a/packages/schema-generator/src/temp/null/validate.ts b/packages/schema-generator/src/temp/null/validate.ts new file mode 100644 index 00000000..b2abe36b --- /dev/null +++ b/packages/schema-generator/src/temp/null/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(nullSchema: t.null): validate { + validateNull.tag = URI.null + function validateNull(u: unknown, path = Array.of()) { + return nullSchema(u) || [NullaryErrors.null(u, path)] + } + return validateNull +} diff --git a/packages/schema-generator/src/temp/number/core.ts b/packages/schema-generator/src/temp/number/core.ts new file mode 100644 index 00000000..5972ae85 --- /dev/null +++ b/packages/schema-generator/src/temp/number/core.ts @@ -0,0 +1,139 @@ +import type { Bounds, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' + + +export { number_ as number } + +interface number_ extends number_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function NumberSchema(src: unknown) { return typeof src === 'number' } +NumberSchema.tag = URI.number +NumberSchema.def = 0 + +const number_ = Object_assign( + NumberSchema, + userDefinitions, +) as number_ + +number_.min = function numberMin(minimum) { + return Object_assign( + boundedNumber({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +number_.max = function numberMax(maximum) { + return Object_assign( + boundedNumber({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +number_.moreThan = function numberMoreThan(exclusiveMinimum) { + return Object_assign( + boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), + { exclusiveMinimum }, + ) +} +number_.lessThan = function numberLessThan(exclusiveMaximum) { + return Object_assign( + boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), + { exclusiveMaximum }, + ) +} +number_.between = function numberBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedNumber({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + number_, + bindUserExtensions(number_, userExtensions), +) + +function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedNumberSchema(u: unknown) { + return typeof u === 'number' && within(bounds)(u) + }, carry, number_) +} + +declare namespace number_ { + interface core extends number_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.number + get def(): this['_type'] + minimum?: number + maximum?: number + exclusiveMinimum?: number + exclusiveMaximum?: number + } + interface methods { + min(minimum: Min): number_.Min + max(maximum: Max): number_.Max + moreThan(moreThan: Min): ExclusiveMin + lessThan(lessThan: Max): ExclusiveMax + between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.between<[min: X, max: Self['maximum']]> + : number_.min + ; + type Max + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.between<[min: Self['minimum'], max: X]> + : number_.max + ; + type ExclusiveMin + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.maxStrictMin<[min: X, Self['maximum']]> + : number_.moreThan + ; + type ExclusiveMax + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.minStrictMax<[Self['minimum'], min: X]> + : number_.lessThan + ; + interface min extends number_ { minimum: Min } + interface max extends number_ { maximum: Max } + interface moreThan extends number_ { exclusiveMinimum: Min } + interface lessThan extends number_ { exclusiveMaximum: Max } + interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } + interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } + interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } + interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } +} diff --git a/packages/schema-generator/src/temp/number/equals.ts b/packages/schema-generator/src/temp/number/equals.ts new file mode 100644 index 00000000..29fcd602 --- /dev/null +++ b/packages/schema-generator/src/temp/number/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { SameValueNumber } from '@traversable/registry' + +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} diff --git a/packages/schema-generator/src/temp/number/extension.ts b/packages/schema-generator/src/temp/number/extension.ts new file mode 100644 index 00000000..1a06ace0 --- /dev/null +++ b/packages/schema-generator/src/temp/number/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let Definitions = { + toString, + equals, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/temp/number/toJsonSchema.ts b/packages/schema-generator/src/temp/number/toJsonSchema.ts new file mode 100644 index 00000000..7146d478 --- /dev/null +++ b/packages/schema-generator/src/temp/number/toJsonSchema.ts @@ -0,0 +1,23 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.number): toJsonSchema { + function numberToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'number' as const, + ...bounds, + } + } + return numberToJsonSchema +} diff --git a/packages/schema-generator/src/temp/number/toString.ts b/packages/schema-generator/src/temp/number/toString.ts new file mode 100644 index 00000000..912565e6 --- /dev/null +++ b/packages/schema-generator/src/temp/number/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } diff --git a/packages/schema-generator/src/temp/number/validate.ts b/packages/schema-generator/src/temp/number/validate.ts new file mode 100644 index 00000000..416f639b --- /dev/null +++ b/packages/schema-generator/src/temp/number/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(numberSchema: S): validate { + validateNumber.tag = URI.number + function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return numberSchema(u) || [NullaryErrors.number(u, path)] + } + return validateNumber +} diff --git a/packages/schema-generator/src/temp/object/core.ts b/packages/schema-generator/src/temp/object/core.ts new file mode 100644 index 00000000..ad856e41 --- /dev/null +++ b/packages/schema-generator/src/temp/object/core.ts @@ -0,0 +1,76 @@ +import type { Force, SchemaOptions as Options, Unknown } from '@traversable/registry' +import { + applyOptions, + Array_isArray, + bindUserExtensions, + has, + _isPredicate, + Object_assign, + Object_keys, + record as record$, + object as object$, + isAnyObject, + symbol, + URI, +} from '@traversable/registry' + +import type { Entry, Optional, Required, Schema, SchemaLike } from '../namespace.js' + +export { object_ as object } + +function object_< + S extends { [x: string]: Schema }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_< + S extends { [x: string]: SchemaLike }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_(schemas: S, options?: Options) { + return object_.def(schemas, options) +} + +interface object_ extends object_.core { + //<%= Types %> +} + +namespace object_ { + export let userDefinitions: Record = { + //<%= Definitions %> + } as object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ + /* v8 ignore next 1 */ + export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const keys = Object_keys(xs) + const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) + const req = keys.filter((k) => !has(symbol.optional)(xs[k])) + const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) + function ObjectSchema(src: unknown) { return predicate(src) } + ObjectSchema.tag = URI.object + ObjectSchema.def = xs + ObjectSchema.opt = opt + ObjectSchema.req = req + Object_assign(ObjectSchema, userDefinitions) + return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) + } +} + +declare namespace object_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: object_.type + tag: URI.object + get def(): S + opt: Optional // TODO: use object_.Opt? + req: Required // TODO: use object_.Req? + } + type Opt = symbol.optional extends keyof S[K] ? never : K + type Req = symbol.optional extends keyof S[K] ? K : never + type type = Force< + & { [K in keyof S as Opt]-?: S[K]['_type' & keyof S[K]] } + & { [K in keyof S as Req]+?: S[K]['_type' & keyof S[K]] } + > +} diff --git a/packages/schema-generator/src/temp/object/equals.ts b/packages/schema-generator/src/temp/object/equals.ts new file mode 100644 index 00000000..ecda0b32 --- /dev/null +++ b/packages/schema-generator/src/temp/object/equals.ts @@ -0,0 +1,55 @@ +import type * as T from '@traversable/registry' +import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | T.Equal +export function equals(objectSchema: t.object): equals> +export function equals(objectSchema: t.object): equals> +export function equals({ def }: t.object): equals> { + function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + for (const k in def) { + const lHas = Object_hasOwn(l, k) + const rHas = Object_hasOwn(r, k) + if (lHas) { + if (!rHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (rHas) { + if (!lHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (!def[k].equals(l[k], r[k])) return false + } + return true + } + return objectEquals +} + +// export type equals = never | T.Equal +// export function equals(objectSchema: t.object): equals> +// export function equals(objectSchema: t.object): equals> +// export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { +// function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { +// if (Object_is(l, r)) return true +// if (!l || typeof l !== 'object' || Array_isArray(l)) return false +// if (!r || typeof r !== 'object' || Array_isArray(r)) return false +// for (const k in def) { +// const lHas = Object_hasOwn(l, k) +// const rHas = Object_hasOwn(r, k) +// if (lHas) { +// if (!rHas) return false +// if (!def[k].equals(l[k], r[k])) return false +// } +// if (rHas) { +// if (!lHas) return false +// if (!def[k].equals(l[k], r[k])) return false +// } +// if (!def[k].equals(l[k], r[k])) return false +// } +// return true +// } +// return objectEquals +// } diff --git a/packages/schema-generator/src/temp/object/extension.ts b/packages/schema-generator/src/temp/object/extension.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/temp/object/extension.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/temp/object/toJsonSchema.ts b/packages/schema-generator/src/temp/object/toJsonSchema.ts new file mode 100644 index 00000000..05dc8f9e --- /dev/null +++ b/packages/schema-generator/src/temp/object/toJsonSchema.ts @@ -0,0 +1,27 @@ +import type { Returns } from '@traversable/registry' +import { fn, Object_keys } from '@traversable/registry' +import type { RequiredKeys } from '@traversable/schema-to-json-schema' +import { isRequired, property } from '@traversable/schema-to-json-schema' +import type { t } from '@traversable/schema-core' + +export interface toJsonSchema = RequiredKeys> { + (): { + type: 'object' + required: { [I in keyof KS]: KS[I] & string } + properties: { [K in keyof T]: Returns } + } +} + +export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema(objectSchema: t.object): toJsonSchema +export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { + const required = Object_keys(def).filter(isRequired(def)) + function objectToJsonSchema() { + return { + type: 'object' as const, + required, + properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), + } + } + return objectToJsonSchema +} diff --git a/packages/schema-generator/src/temp/object/toString.ts b/packages/schema-generator/src/temp/object/toString.ts new file mode 100644 index 00000000..3edc0bb8 --- /dev/null +++ b/packages/schema-generator/src/temp/object/toString.ts @@ -0,0 +1,42 @@ +import type { Join, UnionToTuple } from '@traversable/registry' +import { symbol } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +/** @internal */ +type Symbol_optional = typeof Symbol_optional +const Symbol_optional: typeof symbol.optional = symbol.optional + +/** @internal */ +const hasOptionalSymbol = (u: unknown): u is { toString(): T } => + !!u && typeof u === 'function' + && Symbol_optional in u + && typeof u[Symbol_optional] === 'number' + +/** @internal */ +const hasToString = (x: unknown): x is { toString(): string } => + !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' + +export interface toString> { + (): never + | [keyof T] extends [never] ? '{}' + /* @ts-expect-error */ + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` +} + + +export function toString>(objectSchema: t.object): toString +export function toString({ def }: t.object) { + function objectToString() { + if (!!def && typeof def === 'object') { + const entries = Object.entries(def) + if (entries.length === 0) return '{}' + else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" + }: ${hasToString(x) ? x.toString() : 'unknown' + }`).join(', ') + } }` + } + else return '{ [x: string]: unknown }' + } + + return objectToString +} \ No newline at end of file diff --git a/packages/schema-generator/src/temp/object/validate.ts b/packages/schema-generator/src/temp/object/validate.ts new file mode 100644 index 00000000..eb06d116 --- /dev/null +++ b/packages/schema-generator/src/temp/object/validate.ts @@ -0,0 +1,110 @@ +import { + Array_isArray, + has, + Object_keys, + Object_hasOwn, + typeName, + URI, +} from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { getConfig } from '@traversable/schema-core' +import type { ValidationError, Validator, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors, Errors, UnaryErrors } from '@traversable/derive-validators' + +/** @internal */ +let isObject = (u: unknown): u is { [x: string]: unknown } => + !!u && typeof u === 'object' && !Array_isArray(u) + +/** @internal */ +let isKeyOf = (k: keyof any, u: T): k is keyof T => + !!u && (typeof u === 'function' || typeof u === 'object') && k in u + +/** @internal */ +let isOptional = has('tag', (tag) => tag === URI.optional) + + +export type validate = never | ValidationFn + +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object): validate +export function validate(objectSchema: t.object): validate<{ [x: string]: unknown }> { + validateObject.tag = URI.object + function validateObject(u: unknown, path_ = Array.of()) { + // if (objectSchema(u)) return true + if (!isObject(u)) return [Errors.object(u, path_)] + let errors = Array.of() + let { schema: { optionalTreatment } } = getConfig() + let keys = Object_keys(objectSchema.def) + if (optionalTreatment === 'exactOptional') { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (Object_hasOwn(u, k) && u[k] === undefined) { + if (isOptional(objectSchema.def[k].validate)) { + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] + errors.push(NullaryErrors[tag](...args)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + errors.push(NullaryErrors[tag](u[k], path, tag)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag].invalid(u[k], path)) + } + errors.push(...results) + } + else if (Object_hasOwn(u, k)) { + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + errors.push(...results) + continue + } else { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + } + } + else { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (!Object_hasOwn(u, k)) { + if (!isOptional(objectSchema.def[k].validate)) { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + else { + if (!Object_hasOwn(u, k)) continue + if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { + if (u[k] === undefined) continue + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let j = 0; j < results.length; j++) { + let result = results[j] + errors.push(result) + continue + } + } + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let l = 0; l < results.length; l++) { + let result = results[l] + errors.push(result) + } + } + } + return errors.length === 0 || errors + } + + return validateObject +} diff --git a/packages/schema-generator/src/temp/of/core.ts b/packages/schema-generator/src/temp/of/core.ts new file mode 100644 index 00000000..c02a9210 --- /dev/null +++ b/packages/schema-generator/src/temp/of/core.ts @@ -0,0 +1,47 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +import type { + Entry, + Guard, + Guarded, + SchemaLike, +} from '../namespace.js' + +export interface of extends of.core { + //<%= Types %> +} + +export function of(typeguard: S): Entry +export function of(typeguard: S): of +export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { + typeguard.def = typeguard + return Object_assign(typeguard, of.prototype) +} + +export namespace of { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export let userExtensions: Record = { + //<%= Extensions %> + } + export function def(guard: T): of + /* v8 ignore next 6 */ + export function def(guard: T) { + function InlineSchema(src: unknown) { return guard(src) } + InlineSchema.tag = URI.inline + InlineSchema.def = guard + return InlineSchema + } +} + +export declare namespace of { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: Guarded + tag: URI.inline + get def(): S + } + type type> = never | T +} diff --git a/packages/schema-generator/src/temp/of/equals.ts b/packages/schema-generator/src/temp/of/equals.ts new file mode 100644 index 00000000..38809729 --- /dev/null +++ b/packages/schema-generator/src/temp/of/equals.ts @@ -0,0 +1,6 @@ +import { Equal } from '@traversable/registry' + +export type equals = Equal +export function equals(left: T, right: T): boolean { + return Equal.lax(left, right) +} diff --git a/packages/schema-generator/src/temp/of/extension.ts b/packages/schema-generator/src/temp/of/extension.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/temp/of/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/of/toJsonSchema.ts b/packages/schema-generator/src/temp/of/toJsonSchema.ts new file mode 100644 index 00000000..c8aaf62b --- /dev/null +++ b/packages/schema-generator/src/temp/of/toJsonSchema.ts @@ -0,0 +1,7 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function inlineToJsonSchema(): void { + return void 0 + } + return inlineToJsonSchema +} diff --git a/packages/schema-generator/src/temp/of/toString.ts b/packages/schema-generator/src/temp/of/toString.ts new file mode 100644 index 00000000..417e1048 --- /dev/null +++ b/packages/schema-generator/src/temp/of/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } diff --git a/packages/schema-generator/src/temp/of/validate.ts b/packages/schema-generator/src/temp/of/validate.ts new file mode 100644 index 00000000..c6557c41 --- /dev/null +++ b/packages/schema-generator/src/temp/of/validate.ts @@ -0,0 +1,14 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(inlineSchema: t.of): validate { + validateInline.tag = URI.inline + function validateInline(u: unknown, path = Array.of()) { + return inlineSchema(u) || [NullaryErrors.inline(u, path)] + } + return validateInline +} + diff --git a/packages/schema-generator/src/temp/optional/core.ts b/packages/schema-generator/src/temp/optional/core.ts new file mode 100644 index 00000000..0574d117 --- /dev/null +++ b/packages/schema-generator/src/temp/optional/core.ts @@ -0,0 +1,55 @@ +import type { Unknown } from '@traversable/registry' +import { + bindUserExtensions, + has, + _isPredicate, + optional as optional$, + Object_assign, + symbol, + URI, + isUnknown as isAny, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '../namespace.js' + +export function optional(schema: S): optional +export function optional(schema: S): optional> +export function optional(schema: S): optional { return optional.def(schema) } + +export interface optional extends optional.core { + //<%= Types %> +} + +export namespace optional { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): optional + export function def(x: T) { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = _isPredicate(x) ? optional$(x) : isAny + function OptionalSchema(src: unknown) { return predicate(src) } + OptionalSchema.tag = URI.optional + OptionalSchema.def = x + OptionalSchema[symbol.optional] = 1 + Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) + return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) + } + export const is + : (u: unknown) => u is optional + /* v8 ignore next 1 */ + = has('tag', (u) => u === URI.optional) +} + +export declare namespace optional { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.optional + _type: undefined | S['_type' & keyof S] + def: S + [symbol.optional]: number + } + export type type = never | T +} diff --git a/packages/schema-generator/src/temp/optional/equals.ts b/packages/schema-generator/src/temp/optional/equals.ts new file mode 100644 index 00000000..25944376 --- /dev/null +++ b/packages/schema-generator/src/temp/optional/equals.ts @@ -0,0 +1,13 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal +export function equals(optionalSchema: t.optional): equals +export function equals(optionalSchema: t.optional): equals +export function equals({ def }: t.optional<{ equals: Equal }>): Equal { + return function optionalEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + return def.equals(l, r) + } +} diff --git a/packages/schema-generator/src/temp/optional/extension.ts b/packages/schema-generator/src/temp/optional/extension.ts new file mode 100644 index 00000000..0e7c4478 --- /dev/null +++ b/packages/schema-generator/src/temp/optional/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} + diff --git a/packages/schema-generator/src/temp/optional/toJsonSchema.ts b/packages/schema-generator/src/temp/optional/toJsonSchema.ts new file mode 100644 index 00000000..82bff553 --- /dev/null +++ b/packages/schema-generator/src/temp/optional/toJsonSchema.ts @@ -0,0 +1,19 @@ +import type { Force } from '@traversable/registry' +import type { Returns } from '@traversable/registry' +import { symbol } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' + +type Nullable = Force + +export interface toJsonSchema { + (): Nullable> + [symbol.optional]: number +} + +export function toJsonSchema(optionalSchema: t.optional): toJsonSchema +export function toJsonSchema({ def }: t.optional) { + function optionalToJsonSchema() { return getSchema(def) } + optionalToJsonSchema[symbol.optional] = wrapOptional(def) + return optionalToJsonSchema +} diff --git a/packages/schema-generator/src/temp/optional/toString.ts b/packages/schema-generator/src/temp/optional/toString.ts new file mode 100644 index 00000000..f4c96cc8 --- /dev/null +++ b/packages/schema-generator/src/temp/optional/toString.ts @@ -0,0 +1,15 @@ +import type { t } from '@traversable/schema-core' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType} | undefined)` +} + +export function toString(optionalSchema: t.optional): toString +export function toString({ def }: t.optional): () => string { + function optionalToString(): string { + return '(' + callToString(def) + ' | undefined)' + } + return optionalToString +} diff --git a/packages/schema-generator/src/temp/optional/validate.ts b/packages/schema-generator/src/temp/optional/validate.ts new file mode 100644 index 00000000..70cd1e9d --- /dev/null +++ b/packages/schema-generator/src/temp/optional/validate.ts @@ -0,0 +1,17 @@ +import { URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { Validate, Validator, ValidationFn } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(optionalSchema: t.optional): validate +export function validate(optionalSchema: t.optional): validate +export function validate({ def }: t.optional): ValidationFn { + validateOptional.tag = URI.optional + validateOptional.optional = 1 + function validateOptional(u: unknown, path = Array.of()) { + if (u === void 0) return true + return def.validate(u, path) + } + return validateOptional +} diff --git a/packages/schema-generator/src/temp/record/core.ts b/packages/schema-generator/src/temp/record/core.ts new file mode 100644 index 00000000..c4e54a9a --- /dev/null +++ b/packages/schema-generator/src/temp/record/core.ts @@ -0,0 +1,50 @@ +import type { Unknown } from '@traversable/registry' +import { + isAnyObject, + record as record$, + bindUserExtensions, + _isPredicate, + Object_assign, + URI, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '../namespace.js' + +export function record(schema: S): record +export function record(schema: S): record> +export function record(schema: Schema) { + return record.def(schema) +} + +export interface record extends record.core { + //<%= Types %> +} + +export namespace record { + export let userDefinitions: Record = { + //<%= Definitions %> + } + export function def(x: T): record + /* v8 ignore next 1 */ + export function def(x: unknown): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = _isPredicate(x) ? record$(x) : isAnyObject + function RecordSchema(src: unknown) { return predicate(src) } + RecordSchema.tag = URI.record + RecordSchema.def = x + Object_assign(RecordSchema, record.userDefinitions) + return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) + } +} + +export declare namespace record { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.record + get def(): S + _type: Record + } + export type type> = never | T +} diff --git a/packages/schema-generator/src/temp/record/equals.ts b/packages/schema-generator/src/temp/record/equals.ts new file mode 100644 index 00000000..07addff1 --- /dev/null +++ b/packages/schema-generator/src/temp/record/equals.ts @@ -0,0 +1,32 @@ +import type { Equal } from '@traversable/registry' +import { Array_isArray, Object_is, Object_keys, Object_hasOwn } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = never | Equal +export function equals(recordSchema: t.record): equals +export function equals(recordSchema: t.record): equals +export function equals({ def }: t.record<{ equals: Equal }>): Equal> { + function recordEquals(l: Record, r: Record): boolean { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + const lhs = Object_keys(l) + const rhs = Object_keys(r) + let len = lhs.length + let k: string + if (len !== rhs.length) return false + for (let ix = len; ix-- !== 0;) { + k = lhs[ix] + if (!Object_hasOwn(r, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + len = rhs.length + for (let ix = len; ix-- !== 0;) { + k = rhs[ix] + if (!Object_hasOwn(l, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + return true + } + return recordEquals +} diff --git a/packages/schema-generator/src/temp/record/extension.ts b/packages/schema-generator/src/temp/record/extension.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/temp/record/extension.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/temp/record/toJsonSchema.ts b/packages/schema-generator/src/temp/record/toJsonSchema.ts new file mode 100644 index 00000000..2503d568 --- /dev/null +++ b/packages/schema-generator/src/temp/record/toJsonSchema.ts @@ -0,0 +1,21 @@ +import type { t } from '@traversable/schema-core' +import type * as T from '@traversable/registry' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + type: 'object' + additionalProperties: T.Returns + } +} + +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema(recordSchema: t.record): toJsonSchema +export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { + return function recordToJsonSchema() { + return { + type: 'object' as const, + additionalProperties: getSchema(def), + } + } +} diff --git a/packages/schema-generator/src/temp/record/toString.ts b/packages/schema-generator/src/temp/record/toString.ts new file mode 100644 index 00000000..868f3544 --- /dev/null +++ b/packages/schema-generator/src/temp/record/toString.ts @@ -0,0 +1,17 @@ +import type { Returns } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + /* @ts-expect-error */ + (): never | `Record}>` +} + +export function toString>(recordSchema: S): toString +export function toString(recordSchema: t.record): toString +export function toString({ def }: { def: unknown }): () => string { + function recordToString() { + return `Record` + } + return recordToString +} diff --git a/packages/schema-generator/src/temp/record/validate.ts b/packages/schema-generator/src/temp/record/validate.ts new file mode 100644 index 00000000..6d958004 --- /dev/null +++ b/packages/schema-generator/src/temp/record/validate.ts @@ -0,0 +1,24 @@ +import type { t } from '@traversable/schema-core' +import { Array_isArray, Object_keys, URI } from '@traversable/registry' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = never | ValidationFn +export function validate(recordSchema: t.record): validate +export function validate(recordSchema: t.record): validate +export function validate({ def: { validate = () => true } }: t.record) { + validateRecord.tag = URI.record + function validateRecord(u: unknown, path = Array.of()) { + if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] + let errors = Array.of() + let keys = Object_keys(u) + for (let k of keys) { + let y = u[k] + let results = validate(y, [...path, k]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateRecord +} diff --git a/packages/schema-generator/src/temp/string/core.ts b/packages/schema-generator/src/temp/string/core.ts new file mode 100644 index 00000000..4276ca17 --- /dev/null +++ b/packages/schema-generator/src/temp/string/core.ts @@ -0,0 +1,102 @@ +import type { Bounds, Integer, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_min, + Math_max, + Object_assign, + URI, + within, +} from '@traversable/registry' + +export { string_ as string } + +/** @internal */ +function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedStringSchema(u: unknown) { + return string_(u) && within(bounds)(u.length) + }, carry, string_) +} + +interface string_ extends string_.core { + //<%= Types %> +} + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +function StringSchema(src: unknown) { return typeof src === 'string' } +StringSchema.tag = URI.string +StringSchema.def = '' + +const string_ = Object_assign( + StringSchema, + userDefinitions, +) as string_ + +string_.min = function stringMinLength(minLength) { + return Object_assign( + boundedString({ gte: minLength }, carryover(this, 'minLength')), + { minLength }, + ) +} +string_.max = function stringMaxLength(maxLength) { + return Object_assign( + boundedString({ lte: maxLength }, carryover(this, 'maxLength')), + { maxLength }, + ) +} +string_.between = function stringBetween( + min, + max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max)) { + return Object_assign( + boundedString({ gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) +} + +Object_assign( + string_, + bindUserExtensions(string_, userExtensions), +) + +declare namespace string_ { + interface core extends string_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: string + tag: URI.string + get def(): this['_type'] + } + interface methods { + minLength?: number + maxLength?: number + min>(minLength: Min): string_.Min + max>(maxLength: Max): string_.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maxLength: number }] + ? string_.between<[min: Min, max: Self['maxLength']]> + : string_.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? string_.between<[min: Self['minLength'], max: Max]> + : string_.max + ; + interface min extends string_ { minLength: Min } + interface max extends string_ { maxLength: Max } + interface between extends string_ { + minLength: Bounds[0] + maxLength: Bounds[1] + } +} diff --git a/packages/schema-generator/src/temp/string/equals.ts b/packages/schema-generator/src/temp/string/equals.ts new file mode 100644 index 00000000..b9444108 --- /dev/null +++ b/packages/schema-generator/src/temp/string/equals.ts @@ -0,0 +1,6 @@ +import type { Equal } from '@traversable/registry' + +export type equals = Equal +export function equals(left: string, right: string): boolean { + return left === right +} diff --git a/packages/schema-generator/src/temp/string/extension.ts b/packages/schema-generator/src/temp/string/extension.ts new file mode 100644 index 00000000..c64c1266 --- /dev/null +++ b/packages/schema-generator/src/temp/string/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + toString, + equals, +} + +export let Extensions = { + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/temp/string/toJsonSchema.ts b/packages/schema-generator/src/temp/string/toJsonSchema.ts new file mode 100644 index 00000000..2956c069 --- /dev/null +++ b/packages/schema-generator/src/temp/string/toJsonSchema.ts @@ -0,0 +1,22 @@ +import type { Force, PickIfDefined } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { has } from '@traversable/registry' +import type { SizeBounds } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): Force<{ type: 'string' } & PickIfDefined> +} + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: t.string): () => { type: 'string' } & Partial { + function stringToJsonSchema() { + const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null + const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null + let out: { type: 'string' } & Partial = { type: 'string' } + minLength !== null && void (out.minLength = minLength) + maxLength !== null && void (out.maxLength = maxLength) + + return out + } + return stringToJsonSchema +} diff --git a/packages/schema-generator/src/temp/string/toString.ts b/packages/schema-generator/src/temp/string/toString.ts new file mode 100644 index 00000000..86a98e16 --- /dev/null +++ b/packages/schema-generator/src/temp/string/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'string' } +export function toString(): 'string' { return 'string' } diff --git a/packages/schema-generator/src/temp/string/validate.ts b/packages/schema-generator/src/temp/string/validate.ts new file mode 100644 index 00000000..650ce686 --- /dev/null +++ b/packages/schema-generator/src/temp/string/validate.ts @@ -0,0 +1,13 @@ +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(stringSchema: S): validate { + validateString.tag = URI.string + function validateString(u: unknown, path = Array.of()): true | ValidationError[] { + return stringSchema(u) || [NullaryErrors.number(u, path)] + } + return validateString +} diff --git a/packages/schema-generator/src/temp/symbol/core.ts b/packages/schema-generator/src/temp/symbol/core.ts new file mode 100644 index 00000000..6e192b3e --- /dev/null +++ b/packages/schema-generator/src/temp/symbol/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { symbol_ as symbol } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface symbol_ extends symbol_.core { + //<%= Types %> +} + +function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } +SymbolSchema.tag = URI.symbol +SymbolSchema.def = Symbol() + +const symbol_ = Object_assign( + SymbolSchema, + userDefinitions, +) as symbol_ + +Object_assign(symbol_, userExtensions) + +declare namespace symbol_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.symbol + _type: symbol + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/temp/symbol/equals.ts b/packages/schema-generator/src/temp/symbol/equals.ts new file mode 100644 index 00000000..f3bb7486 --- /dev/null +++ b/packages/schema-generator/src/temp/symbol/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' + +export type equals = Equal +export function equals(left: symbol, right: symbol): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/src/temp/symbol/extension.ts b/packages/schema-generator/src/temp/symbol/extension.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/temp/symbol/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/symbol/toJsonSchema.ts b/packages/schema-generator/src/temp/symbol/toJsonSchema.ts new file mode 100644 index 00000000..7046b08e --- /dev/null +++ b/packages/schema-generator/src/temp/symbol/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function symbolToJsonSchema() { return void 0 } + return symbolToJsonSchema +} diff --git a/packages/schema-generator/src/temp/symbol/toString.ts b/packages/schema-generator/src/temp/symbol/toString.ts new file mode 100644 index 00000000..5651fe27 --- /dev/null +++ b/packages/schema-generator/src/temp/symbol/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'symbol' } +export function toString(): 'symbol' { return 'symbol' } diff --git a/packages/schema-generator/src/temp/symbol/validate.ts b/packages/schema-generator/src/temp/symbol/validate.ts new file mode 100644 index 00000000..302e11f2 --- /dev/null +++ b/packages/schema-generator/src/temp/symbol/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(symbolSchema: t.symbol): validate { + validateSymbol.tag = URI.symbol + function validateSymbol(u: unknown, path = Array.of()) { + return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] + } + return validateSymbol +} diff --git a/packages/schema-generator/src/temp/tuple/core.ts b/packages/schema-generator/src/temp/tuple/core.ts new file mode 100644 index 00000000..f2b9f6bb --- /dev/null +++ b/packages/schema-generator/src/temp/tuple/core.ts @@ -0,0 +1,86 @@ +import type { + SchemaOptions as Options, + TypeError, + Unknown +} from '@traversable/registry' + +import { + Array_isArray, + bindUserExtensions, + getConfig, + has, + _isPredicate, + Object_assign, + parseArgs, + symbol, + tuple as tuple$, + URI, +} from '@traversable/registry' + +import type { + Entry, + FirstOptionalItem, + invalid, + Schema, + SchemaLike, + TupleType, + ValidateTuple +} from '../namespace.js' + +import type { optional } from './optional.js' + +export { tuple } + +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { + return tuple.def(...parseArgs(getConfig().schema, args)) +} + +interface tuple extends tuple.core { + //<%= Types %> +} + +namespace tuple { + export let userDefinitions: Record = { + //<%= Definitions %> + } as tuple + export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple + /* v8 ignore next 1 */ + export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { + let userExtensions: Record = { + //<%= Extensions %> + } + const opt = opt_ || xs.findIndex(has(symbol.optional)) + const options = { + ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) + } satisfies tuple.InternalOptions + const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) + function TupleSchema(src: unknown) { return predicate(src) } + TupleSchema.tag = URI.tuple + TupleSchema.def = xs + TupleSchema.opt = opt + Object_assign(TupleSchema, tuple.userDefinitions) + return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) + } +} + +declare namespace tuple { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.tuple + _type: TupleType + opt: FirstOptionalItem + def: S + } + type type> = never | T + type InternalOptions = { minLength?: number } + type validate = ValidateTuple> + + type from + = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T +} diff --git a/packages/schema-generator/src/temp/tuple/equals.ts b/packages/schema-generator/src/temp/tuple/equals.ts new file mode 100644 index 00000000..66adadaa --- /dev/null +++ b/packages/schema-generator/src/temp/tuple/equals.ts @@ -0,0 +1,27 @@ +import type { Equal } from '@traversable/registry' +import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = Equal + +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple): equals +export function equals(tupleSchema: t.tuple) { + function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + for (let ix = tupleSchema.def.length; ix-- !== 0;) { + if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue + if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false + if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false + if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { + if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false + } + } + return true + } + return false + } + return tupleEquals +} diff --git a/packages/schema-generator/src/temp/tuple/extension.ts b/packages/schema-generator/src/temp/tuple/extension.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/temp/tuple/extension.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/temp/tuple/toJsonSchema.ts b/packages/schema-generator/src/temp/tuple/toJsonSchema.ts new file mode 100644 index 00000000..a71ee145 --- /dev/null +++ b/packages/schema-generator/src/temp/tuple/toJsonSchema.ts @@ -0,0 +1,37 @@ +import type { Returns } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' +import type { MinItems } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { + type: 'array', + items: { [I in keyof T]: Returns } + additionalItems: false + minItems: MinItems + maxItems: T['length' & keyof T] + } +} + +export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema +export function toJsonSchema({ def }: t.tuple): () => { + type: 'array' + items: unknown + additionalItems: false + minItems?: {} + maxItems?: number +} { + function tupleToJsonSchema() { + let min = minItems(def) + let max = def.length + let items = applyTupleOptionality(def, { min, max }) + return { + type: 'array' as const, + additionalItems: false as const, + items, + minItems: min, + maxItems: max, + } + } + return tupleToJsonSchema +} diff --git a/packages/schema-generator/src/temp/tuple/toString.ts b/packages/schema-generator/src/temp/tuple/toString.ts new file mode 100644 index 00000000..638daca9 --- /dev/null +++ b/packages/schema-generator/src/temp/tuple/toString.ts @@ -0,0 +1,27 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray, has, URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { hasToString } from '@traversable/schema-to-string' + +export interface toString { + (): never | `[${Join<{ + [I in keyof T]: `${ + /* @ts-expect-error */ + T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType + }` + }, ', '>}]` +} + +export function toString(tupleSchema: t.tuple): toString +export function toString(tupleSchema: t.tuple): () => string { + let isOptional = has('tag', (tag) => tag === URI.optional) + function stringToString() { + return Array_isArray(tupleSchema.def) + ? `[${tupleSchema.def.map( + (x) => isOptional(x) + ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` + : hasToString(x) ? x.toString() : 'unknown' + ).join(', ')}]` : 'unknown[]' + } + return stringToString +} diff --git a/packages/schema-generator/src/temp/tuple/validate.ts b/packages/schema-generator/src/temp/tuple/validate.ts new file mode 100644 index 00000000..5b065453 --- /dev/null +++ b/packages/schema-generator/src/temp/tuple/validate.ts @@ -0,0 +1,35 @@ +import { Array_isArray, has, URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + +export type validate = Validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): validate +export function validate(tupleSchema: t.tuple<[...S]>): Validate { + validateTuple.tag = URI.tuple + let isOptional = has('tag', (tag) => tag === URI.optional) + function validateTuple(u: unknown, path = Array.of()) { + let errors = Array.of() + if (!Array_isArray(u)) return [Errors.array(u, path)] + for (let i = 0; i < tupleSchema.def.length; i++) { + if (!(i in u) && !(isOptional(tupleSchema.def[i].validate))) { + errors.push(Errors.missingIndex(u, [...path, i])) + continue + } + let results = tupleSchema.def[i].validate(u[i], [...path, i]) + if (results !== true) { + for (let j = 0; j < results.length; j++) errors.push(results[j]) + results.push(Errors.arrayElement(u[i], [...path, i])) + } + } + if (u.length > tupleSchema.def.length) { + for (let k = tupleSchema.def.length; k < u.length; k++) { + let excess = u[k] + errors.push(Errors.excessItems(excess, [...path, k])) + } + } + return errors.length === 0 || errors + } + return validateTuple +} diff --git a/packages/schema-generator/src/temp/undefined/core.ts b/packages/schema-generator/src/temp/undefined/core.ts new file mode 100644 index 00000000..0115f168 --- /dev/null +++ b/packages/schema-generator/src/temp/undefined/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { undefined_ as undefined } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface undefined_ extends undefined_.core { + //<%= Types %> +} + +function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } +UndefinedSchema.tag = URI.undefined +UndefinedSchema.def = void 0 as undefined + +const undefined_ = Object_assign( + UndefinedSchema, + userDefinitions, +) as undefined_ + +Object_assign(undefined_, userExtensions) + +declare namespace undefined_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.undefined + _type: undefined + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/temp/undefined/equals.ts b/packages/schema-generator/src/temp/undefined/equals.ts new file mode 100644 index 00000000..75156d56 --- /dev/null +++ b/packages/schema-generator/src/temp/undefined/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' + +export type equals = Equal +export function equals(left: undefined, right: undefined): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/src/temp/undefined/extension.ts b/packages/schema-generator/src/temp/undefined/extension.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/temp/undefined/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/undefined/toJsonSchema.ts b/packages/schema-generator/src/temp/undefined/toJsonSchema.ts new file mode 100644 index 00000000..be46c306 --- /dev/null +++ b/packages/schema-generator/src/temp/undefined/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function undefinedToJsonSchema(): void { return void 0 } + return undefinedToJsonSchema +} diff --git a/packages/schema-generator/src/temp/undefined/toString.ts b/packages/schema-generator/src/temp/undefined/toString.ts new file mode 100644 index 00000000..a48b744b --- /dev/null +++ b/packages/schema-generator/src/temp/undefined/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'undefined' } +export function toString(): 'undefined' { return 'undefined' } diff --git a/packages/schema-generator/src/temp/undefined/validate.ts b/packages/schema-generator/src/temp/undefined/validate.ts new file mode 100644 index 00000000..d69c9d9e --- /dev/null +++ b/packages/schema-generator/src/temp/undefined/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(undefinedSchema: t.undefined): validate { + validateUndefined.tag = URI.undefined + function validateUndefined(u: unknown, path = Array.of()) { + return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] + } + return validateUndefined +} diff --git a/packages/schema-generator/src/temp/union/core.ts b/packages/schema-generator/src/temp/union/core.ts new file mode 100644 index 00000000..1e3705ee --- /dev/null +++ b/packages/schema-generator/src/temp/union/core.ts @@ -0,0 +1,50 @@ +import type { Unknown } from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + isUnknown as isAny, + Object_assign, + union as union$, + URI, +} from '@traversable/registry' + +import type { Entry, Schema, SchemaLike } from '../namespace.js' + +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: unknown[]) { + return union.def(schemas) +} + +export interface union extends union.core { + //<%= Types %> +} + +export namespace union { + export let userDefinitions: Record = { + //<%= Definitions %> + } as Partial> + export function def(xs: T): union + /* v8 ignore next 1 */ + export function def(xs: unknown[]) { + let userExtensions: Record = { + //<%= Extensions %> + } + const predicate = xs.every(_isPredicate) ? union$(xs) : isAny + function UnionSchema(src: unknown): src is unknown { return predicate(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.userDefinitions) + return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) + } +} + +export declare namespace union { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.union + _type: union.type + get def(): S + } + type type = never | T +} diff --git a/packages/schema-generator/src/temp/union/equals.ts b/packages/schema-generator/src/temp/union/equals.ts new file mode 100644 index 00000000..c0275e69 --- /dev/null +++ b/packages/schema-generator/src/temp/union/equals.ts @@ -0,0 +1,16 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' +import type { t } from '@traversable/schema-core' + +export type equals = Equal +export function equals(unionSchema: t.union<[...S]>): equals +export function equals(unionSchema: t.union<[...S]>): equals +export function equals({ def }: t.union<{ equals: Equal }[]>): Equal { + function unionEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (def[ix].equals(l, r)) return true + return false + } + return unionEquals +} diff --git a/packages/schema-generator/src/temp/union/extension.ts b/packages/schema-generator/src/temp/union/extension.ts new file mode 100644 index 00000000..ee871114 --- /dev/null +++ b/packages/schema-generator/src/temp/union/extension.ts @@ -0,0 +1,20 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = {} + +export let Extensions = { + toString, + equals, + toJsonSchema, + validate, +} diff --git a/packages/schema-generator/src/temp/union/toJsonSchema.ts b/packages/schema-generator/src/temp/union/toJsonSchema.ts new file mode 100644 index 00000000..f9467612 --- /dev/null +++ b/packages/schema-generator/src/temp/union/toJsonSchema.ts @@ -0,0 +1,17 @@ +import type { Returns } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { getSchema } from '@traversable/schema-to-json-schema' + +export interface toJsonSchema { + (): { anyOf: { [I in keyof T]: Returns } } +} + +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema(unionSchema: t.union): toJsonSchema +export function toJsonSchema({ def }: t.union): () => {} { + return function unionToJsonSchema() { + return { + anyOf: def.map(getSchema) + } + } +} diff --git a/packages/schema-generator/src/temp/union/toString.ts b/packages/schema-generator/src/temp/union/toString.ts new file mode 100644 index 00000000..d9c0f3d1 --- /dev/null +++ b/packages/schema-generator/src/temp/union/toString.ts @@ -0,0 +1,18 @@ +import type { Join } from '@traversable/registry' +import { Array_isArray } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import { callToString } from '@traversable/schema-to-string' + +export interface toString { + (): never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` +} + +export function toString(unionSchema: t.union): toString +export function toString({ def }: t.union): () => string { + function unionToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' + } + return unionToString +} diff --git a/packages/schema-generator/src/temp/union/validate.ts b/packages/schema-generator/src/temp/union/validate.ts new file mode 100644 index 00000000..95b1f0f0 --- /dev/null +++ b/packages/schema-generator/src/temp/union/validate.ts @@ -0,0 +1,26 @@ +import { URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' +import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' + +export type validate = Validate + +export function validate(unionSchema: t.union): validate +export function validate(unionSchema: t.union): validate +export function validate({ def }: t.union) { + validateUnion.tag = URI.union + function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { + // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results === true) { + // validateUnion.optional = 0 + return true + } + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + // validateUnion.optional = 0 + return errors.length === 0 || errors + } + return validateUnion +} diff --git a/packages/schema-generator/src/temp/unknown/core.ts b/packages/schema-generator/src/temp/unknown/core.ts new file mode 100644 index 00000000..0a190db3 --- /dev/null +++ b/packages/schema-generator/src/temp/unknown/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { unknown_ as unknown } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface unknown_ extends unknown_.core { + //<%= Types %> +} + +function UnknownSchema(src: unknown): src is unknown { return true } +UnknownSchema.tag = URI.unknown +UnknownSchema.def = void 0 as unknown + +const unknown_ = Object_assign( + UnknownSchema, + userDefinitions, +) as unknown_ + +Object_assign(unknown_, userExtensions) + +declare namespace unknown_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.unknown + _type: unknown + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/temp/unknown/equals.ts b/packages/schema-generator/src/temp/unknown/equals.ts new file mode 100644 index 00000000..ccd2a780 --- /dev/null +++ b/packages/schema-generator/src/temp/unknown/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' + +export type equals = Equal +export function equals(left: any, right: any): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/src/temp/unknown/extension.ts b/packages/schema-generator/src/temp/unknown/extension.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/temp/unknown/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/unknown/toJsonSchema.ts b/packages/schema-generator/src/temp/unknown/toJsonSchema.ts new file mode 100644 index 00000000..8d5be5a0 --- /dev/null +++ b/packages/schema-generator/src/temp/unknown/toJsonSchema.ts @@ -0,0 +1,5 @@ +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return anyToJsonSchema +} diff --git a/packages/schema-generator/src/temp/unknown/toString.ts b/packages/schema-generator/src/temp/unknown/toString.ts new file mode 100644 index 00000000..417e1048 --- /dev/null +++ b/packages/schema-generator/src/temp/unknown/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } diff --git a/packages/schema-generator/src/temp/unknown/validate.ts b/packages/schema-generator/src/temp/unknown/validate.ts new file mode 100644 index 00000000..9c4298c0 --- /dev/null +++ b/packages/schema-generator/src/temp/unknown/validate.ts @@ -0,0 +1,10 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(_?: t.unknown): validate { + validateUnknown.tag = URI.unknown + function validateUnknown() { return true as const } + return validateUnknown +} diff --git a/packages/schema-generator/src/temp/void/core.ts b/packages/schema-generator/src/temp/void/core.ts new file mode 100644 index 00000000..d178213e --- /dev/null +++ b/packages/schema-generator/src/temp/void/core.ts @@ -0,0 +1,36 @@ +import type { Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' + +export { void_ as void, void_ } + +export let userDefinitions: Record = { + //<%= Definitions %> +} + +export let userExtensions: Record = { + //<%= Extensions %> +} + +interface void_ extends void_.core { + //<%= Types %> +} + +function VoidSchema(src: unknown): src is void { return src === void 0 } +VoidSchema.tag = URI.void +VoidSchema.def = void 0 as void + +const void_ = Object_assign( + VoidSchema, + userDefinitions, +) as void_ + +Object_assign(void_, userExtensions) + +declare namespace void_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.void + _type: void + get def(): this['_type'] + } +} diff --git a/packages/schema-generator/src/temp/void/equals.ts b/packages/schema-generator/src/temp/void/equals.ts new file mode 100644 index 00000000..d11d89e3 --- /dev/null +++ b/packages/schema-generator/src/temp/void/equals.ts @@ -0,0 +1,7 @@ +import type { Equal } from '@traversable/registry' +import { Object_is } from '@traversable/registry' + +export type equals = Equal +export function equals(left: void, right: void): boolean { + return Object_is(left, right) +} diff --git a/packages/schema-generator/src/temp/void/extension.ts b/packages/schema-generator/src/temp/void/extension.ts new file mode 100644 index 00000000..7a2b8c12 --- /dev/null +++ b/packages/schema-generator/src/temp/void/extension.ts @@ -0,0 +1,21 @@ +import { equals } from './equals.js' +import { toJsonSchema } from './toJsonSchema.js' +import { toString } from './toString.js' +import { validate } from './validate.js' + +export interface Types { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let Definitions = { + equals, + toJsonSchema, + toString, +} + +export let Extensions = { + validate, +} diff --git a/packages/schema-generator/src/temp/void/toJsonSchema.ts b/packages/schema-generator/src/temp/void/toJsonSchema.ts new file mode 100644 index 00000000..d636b569 --- /dev/null +++ b/packages/schema-generator/src/temp/void/toJsonSchema.ts @@ -0,0 +1,7 @@ +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function voidToJsonSchema(): void { + return void 0 + } + return voidToJsonSchema +} diff --git a/packages/schema-generator/src/temp/void/toString.ts b/packages/schema-generator/src/temp/void/toString.ts new file mode 100644 index 00000000..487d08b3 --- /dev/null +++ b/packages/schema-generator/src/temp/void/toString.ts @@ -0,0 +1,2 @@ +export interface toString { (): 'void' } +export function toString(): 'void' { return 'void' } diff --git a/packages/schema-generator/src/temp/void/validate.ts b/packages/schema-generator/src/temp/void/validate.ts new file mode 100644 index 00000000..a67fc4e4 --- /dev/null +++ b/packages/schema-generator/src/temp/void/validate.ts @@ -0,0 +1,13 @@ +import type { t } from '@traversable/schema-core' +import { URI } from '@traversable/registry' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + +export type validate = ValidationFn +export function validate(voidSchema: t.void): validate { + validateVoid.tag = URI.void + function validateVoid(u: unknown, path = Array.of()) { + return voidSchema(u) || [NullaryErrors.void(u, path)] + } + return validateVoid +} diff --git a/packages/schema-generator/tsconfig.build.json b/packages/schema-generator/tsconfig.build.json index be49007b..e7875781 100644 --- a/packages/schema-generator/tsconfig.build.json +++ b/packages/schema-generator/tsconfig.build.json @@ -7,5 +7,12 @@ "outDir": "build/esm", "stripInternal": true }, - "references": [{ "path": "../registry" }, { "path": "../schema-core" }] -} + "references": [ + { "path": "../derive-validators" }, + { "path": "../derive-equals" }, + { "path": "../registry" }, + { "path": "../schema-core" }, + { "path": "../schema-to-json-schema" }, + { "path": "../schema-to-string" } + ] +} \ No newline at end of file diff --git a/packages/schema-generator/tsconfig.src.json b/packages/schema-generator/tsconfig.src.json index 702668d2..384d2be2 100644 --- a/packages/schema-generator/tsconfig.src.json +++ b/packages/schema-generator/tsconfig.src.json @@ -6,6 +6,13 @@ "types": ["node"], "outDir": "build/src" }, - "references": [{ "path": "../registry" }, { "path": "../schema-core" }], + "references": [ + { "path": "../derive-validators" }, + { "path": "../derive-equals" }, + { "path": "../registry" }, + { "path": "../schema-core" }, + { "path": "../schema-to-json-schema" }, + { "path": "../schema-to-string" } + ], "include": ["src"] } diff --git a/packages/schema-generator/tsconfig.test.json b/packages/schema-generator/tsconfig.test.json index 46c1bf86..7277a1a7 100644 --- a/packages/schema-generator/tsconfig.test.json +++ b/packages/schema-generator/tsconfig.test.json @@ -7,8 +7,8 @@ "noEmit": true }, "references": [ - { "path": "tsconfig.src.json" }, { "path": "../derive-validators" }, + { "path": "../derive-equals" }, { "path": "../registry" }, { "path": "../schema-core" }, { "path": "../schema-to-json-schema" }, diff --git a/packages/schema-to-json-schema/src/schemas/object.ts b/packages/schema-to-json-schema/src/schemas/object.ts index bc79c90c..05dc8f9e 100644 --- a/packages/schema-to-json-schema/src/schemas/object.ts +++ b/packages/schema-to-json-schema/src/schemas/object.ts @@ -2,7 +2,7 @@ import type { Returns } from '@traversable/registry' import { fn, Object_keys } from '@traversable/registry' import type { RequiredKeys } from '@traversable/schema-to-json-schema' import { isRequired, property } from '@traversable/schema-to-json-schema' -import { t } from '@traversable/schema-core' +import type { t } from '@traversable/schema-core' export interface toJsonSchema = RequiredKeys> { (): { diff --git a/packages/schema-to-json-schema/src/schemas/tuple.ts b/packages/schema-to-json-schema/src/schemas/tuple.ts index ed4cf900..a71ee145 100644 --- a/packages/schema-to-json-schema/src/schemas/tuple.ts +++ b/packages/schema-to-json-schema/src/schemas/tuple.ts @@ -1,5 +1,5 @@ import type { Returns } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import type { t } from '@traversable/schema-core' import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' import type { MinItems } from '@traversable/schema-to-json-schema' diff --git a/packages/schema-to-json-schema/src/schemas/union.ts b/packages/schema-to-json-schema/src/schemas/union.ts index 850f9f66..f9467612 100644 --- a/packages/schema-to-json-schema/src/schemas/union.ts +++ b/packages/schema-to-json-schema/src/schemas/union.ts @@ -1,5 +1,5 @@ import type { Returns } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import type { t } from '@traversable/schema-core' import { getSchema } from '@traversable/schema-to-json-schema' export interface toJsonSchema { diff --git a/packages/schema-to-string/src/schemas/object.ts b/packages/schema-to-string/src/schemas/object.ts index ad084ffa..3edc0bb8 100644 --- a/packages/schema-to-string/src/schemas/object.ts +++ b/packages/schema-to-string/src/schemas/object.ts @@ -1,6 +1,6 @@ import type { Join, UnionToTuple } from '@traversable/registry' import { symbol } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import type { t } from '@traversable/schema-core' /** @internal */ type Symbol_optional = typeof Symbol_optional @@ -23,8 +23,9 @@ export interface toString> : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` } + export function toString>(objectSchema: t.object): toString -export function toString({ def }: t.object) { +export function toString({ def }: t.object) { function objectToString() { if (!!def && typeof def === 'object') { const entries = Object.entries(def) @@ -38,4 +39,4 @@ export function toString({ def }: t.object) { } return objectToString -} +} \ No newline at end of file diff --git a/packages/schema-to-string/src/schemas/tuple.ts b/packages/schema-to-string/src/schemas/tuple.ts index b04c3381..638daca9 100644 --- a/packages/schema-to-string/src/schemas/tuple.ts +++ b/packages/schema-to-string/src/schemas/tuple.ts @@ -1,6 +1,6 @@ import type { Join } from '@traversable/registry' -import { Array_isArray } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import { Array_isArray, has, URI } from '@traversable/registry' +import type { t } from '@traversable/schema-core' import { hasToString } from '@traversable/schema-to-string' export interface toString { @@ -14,10 +14,11 @@ export interface toString { export function toString(tupleSchema: t.tuple): toString export function toString(tupleSchema: t.tuple): () => string { + let isOptional = has('tag', (tag) => tag === URI.optional) function stringToString() { return Array_isArray(tupleSchema.def) ? `[${tupleSchema.def.map( - (x) => t.optional.is(x) + (x) => isOptional(x) ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` : hasToString(x) ? x.toString() : 'unknown' ).join(', ')}]` : 'unknown[]' diff --git a/packages/schema-to-string/src/schemas/union.ts b/packages/schema-to-string/src/schemas/union.ts index 6429d3e1..d9c0f3d1 100644 --- a/packages/schema-to-string/src/schemas/union.ts +++ b/packages/schema-to-string/src/schemas/union.ts @@ -1,6 +1,6 @@ import type { Join } from '@traversable/registry' import { Array_isArray } from '@traversable/registry' -import { t } from '@traversable/schema-core' +import type { t } from '@traversable/schema-core' import { callToString } from '@traversable/schema-to-string' export interface toString { diff --git a/packages/schema/src/__schemas__/any.ts b/packages/schema/src/__schemas__/any.ts index 9166a175..7ada302b 100644 --- a/packages/schema/src/__schemas__/any.ts +++ b/packages/schema/src/__schemas__/any.ts @@ -31,11 +31,11 @@ export function toString(): 'any' { return 'any' } ////////////////////// ////////////////////// /// validate /// -export type validate = ValidationFn -export function validate(_?: t.unknown): validate { - validateUnknown.tag = URI.unknown - function validateUnknown() { return true as const } - return validateUnknown +export type validate = ValidationFn +export function validate(_?: any_): validate { + validateAny.tag = URI.any + function validateAny() { return true as const } + return validateAny } /// validate /// ////////////////////// diff --git a/packages/schema/src/__schemas__/eq.ts b/packages/schema/src/__schemas__/eq.ts index cdb625fd..eb99ab5f 100644 --- a/packages/schema/src/__schemas__/eq.ts +++ b/packages/schema/src/__schemas__/eq.ts @@ -15,10 +15,11 @@ import { bindUserExtensions, Equal, getConfig, + laxEquals, Object_assign, URI } from '@traversable/registry' -import { t } from '../_exports.js' +import type { t } from '../_exports.js' import { stringify } from '@traversable/schema-to-string' import type { Validate } from '@traversable/derive-validators' import { Errors } from '@traversable/derive-validators' @@ -27,7 +28,9 @@ import { Errors } from '@traversable/derive-validators' export type equals = never | Equal export function equals(eqSchema: eq): equals export function equals(): Equal { - return (left: unknown, right: unknown) => eq(left)(right) + return function eqEquals(left: any, right: any) { + return laxEquals(left, right) + } } /// equals /// //////////////////// diff --git a/packages/schema/src/__schemas__/object.ts b/packages/schema/src/__schemas__/object.ts index d3e7e32a..68b7ae4c 100644 --- a/packages/schema/src/__schemas__/object.ts +++ b/packages/schema/src/__schemas__/object.ts @@ -36,7 +36,8 @@ import type { Schema, SchemaLike } from '../_namespace.js' -import { getConfig, t } from '../_exports.js' +import type { t } from '../_exports.js' +import { getConfig } from '../_exports.js' import type { RequiredKeys } from '@traversable/schema-to-json-schema' import { isRequired, property } from '@traversable/schema-to-json-schema' import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' @@ -290,16 +291,13 @@ declare namespace object_ { _type: object_.type tag: URI.object get def(): S - opt: Optional - req: Required + opt: Optional // TODO: use object_.Opt? + req: Required // TODO: use object_.Req? } - type type< - S, - Opt extends Optional = Optional, - Req extends Required = Required, - T = Force< - & { [K in Req]-?: S[K]['_type' & keyof S[K]] } - & { [K in Opt]+?: S[K]['_type' & keyof S[K]] } - > - > = never | T + type Opt = symbol.optional extends keyof S[K] ? never : K + type Req = symbol.optional extends keyof S[K] ? K : never + type type = Force< + & { [K in keyof S as Opt]-?: S[K]['_type' & keyof S[K]] } + & { [K in keyof S as Req]+?: S[K]['_type' & keyof S[K]] } + > } diff --git a/packages/schema/src/__schemas__/optional.ts b/packages/schema/src/__schemas__/optional.ts index 5e62c6da..426e5843 100644 --- a/packages/schema/src/__schemas__/optional.ts +++ b/packages/schema/src/__schemas__/optional.ts @@ -20,7 +20,7 @@ import { URI } from '@traversable/registry' import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import { t } from '../_exports.js' +import type { t } from '../_exports.js' import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' import { callToString } from '@traversable/schema-to-string' import type { Validate, ValidationFn, Validator } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/tuple.ts b/packages/schema/src/__schemas__/tuple.ts index 3fc13957..eb0603bc 100644 --- a/packages/schema/src/__schemas__/tuple.ts +++ b/packages/schema/src/__schemas__/tuple.ts @@ -34,7 +34,7 @@ import type { ValidateTuple } from '../_namespace.js' import type { optional } from './optional.js' -import { t } from '../_exports.js' +import type { t } from '../_exports.js' import type { MinItems } from '@traversable/schema-to-json-schema' import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' import { hasToString } from '@traversable/schema-to-string' @@ -116,10 +116,11 @@ export interface toString { export function toString(tupleSchema: tuple): toString export function toString(tupleSchema: tuple): () => string { + let isOptional = has('tag', (tag) => tag === URI.optional) function stringToString() { return Array_isArray(tupleSchema.def) ? `[${tupleSchema.def.map( - (x) => t.optional.is(x) + (x) => isOptional(x) ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` : hasToString(x) ? x.toString() : 'unknown' ).join(', ')}]` : 'unknown[]' @@ -135,11 +136,12 @@ export function validate(tupleSchema: tuple<[... export function validate(tupleSchema: tuple<[...S]>): validate export function validate(tupleSchema: tuple<[...S]>): Validate { validateTuple.tag = URI.tuple + let isOptional = has('tag', (tag) => tag === URI.optional) function validateTuple(u: unknown, path = Array.of()) { let errors = Array.of() if (!Array_isArray(u)) return [Errors.array(u, path)] for (let i = 0; i < tupleSchema.def.length; i++) { - if (!(i in u) && !(t.optional.is(tupleSchema.def[i].validate))) { + if (!(i in u) && !(isOptional(tupleSchema.def[i].validate))) { errors.push(Errors.missingIndex(u, [...path, i])) continue } diff --git a/packages/schema/src/__schemas__/union.ts b/packages/schema/src/__schemas__/union.ts index fdf07920..0bf23565 100644 --- a/packages/schema/src/__schemas__/union.ts +++ b/packages/schema/src/__schemas__/union.ts @@ -19,7 +19,7 @@ import { URI } from '@traversable/registry' import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import { t } from '../_exports.js' +import type { t } from '../_exports.js' import { getSchema } from '@traversable/schema-to-json-schema' import { callToString } from '@traversable/schema-to-string' import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/unknown.ts b/packages/schema/src/__schemas__/unknown.ts index be368504..caaf06c6 100644 --- a/packages/schema/src/__schemas__/unknown.ts +++ b/packages/schema/src/__schemas__/unknown.ts @@ -31,11 +31,11 @@ export function toString(): 'unknown' { return 'unknown' } ////////////////////// ////////////////////// /// validate /// -export type validate = ValidationFn -export function validate(_?: t.any): validate { - validateAny.tag = URI.any - function validateAny() { return true as const } - return validateAny +export type validate = ValidationFn +export function validate(_?: unknown_): validate { + validateUnknown.tag = URI.unknown + function validateUnknown() { return true as const } + return validateUnknown } /// validate /// ////////////////////// diff --git a/packages/schema/src/build.ts b/packages/schema/src/build.ts index d5ebdfae..3d60face 100755 --- a/packages/schema/src/build.ts +++ b/packages/schema/src/build.ts @@ -151,6 +151,7 @@ let LibOptions = t.object({ type BuildOptions = t.typeof let BuildOptions = t.object({ dryRun: t.optional(t.boolean), + skipCleanup: t.optional(t.boolean), postProcessor: (x): x is PostProcessor => typeof x === 'function', excludeSchemas: t.optional(t.union(t.array(t.string), t.null)), getSourceDir: t.optional((x): x is (() => string) => typeof x === 'function'), @@ -204,6 +205,7 @@ let defaultLibs = { let defaultOptions = { dryRun: false, + skipCleanup: false, postProcessor: defaultPostProcessor, excludeSchemas: null, getExtensionFilesDir: () => PATH.extensionsDir, @@ -239,6 +241,7 @@ function parseOptions({ getTempDir = defaultOptions.getTempDir, libs, postProcessor = defaultOptions.postProcessor, + skipCleanup = defaultOptions.skipCleanup, }: Options = defaultOptions): Config { return { dryRun, @@ -247,6 +250,7 @@ function parseOptions({ libs: fn.map(libs, parseLibOptions), namespaceFile: getNamespaceFile(), postProcessor, + skipCleanup, sourceDir: getSourceDir(), targetDir: getTargetDir(), tempDir: getTempDir(), @@ -458,7 +462,16 @@ function build(options: Options) { void ensureDir($.targetDir, $) void writeSchemas($, sources, targets) void writeNamespaceFile($, sources) - void cleanupTempDir($) + if ($.skipCleanup) { + console.group('\n\n[[SKIP_CLEANUP]]: `build`\n') + console.debug('\n`build` received \'skipCleanup\': true. ' + $.tempDir + ' was not removed.') + console.groupEnd() + return void 0 + } + else void cleanupTempDir($) } -build(defaultOptions) +build({ + ...defaultOptions, + // skipCleanup: true, +}) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fc8ea261..6c2dd474 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -291,6 +291,9 @@ importers: '@clack/prompts': specifier: ^0.10.1 version: 0.10.1 + '@traversable/derive-equals': + specifier: workspace:^ + version: link:../derive-equals/dist '@traversable/derive-validators': specifier: workspace:^ version: link:../derive-validators/dist diff --git a/vite.config.ts b/vite.config.ts index f2b4632d..c5e169ad 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -30,7 +30,7 @@ export default defineConfig({ enabled: true, reporter: ['html'], reportsDirectory: './config/coverage', - thresholds: { "100": true }, + // thresholds: { "100": true }, }, disableConsoleIntercept: true, fakeTimers: { toFake: undefined }, From a7f2fe5d99f4c32ffbe423174b5a00b33df25fe3 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Sun, 13 Apr 2025 21:38:52 -0500 Subject: [PATCH 35/45] reboot working --- README.md | 8 +- examples/sandbox/src/lib/react.ts | 2 +- packages/schema-generator/package.json | 7 +- .../src/__generated__/__manifest__.ts | 14 +- .../schema-generator/src/__schemas__/any.ts | 80 ----- .../schema-generator/src/__schemas__/array.ts | 250 ------------- .../src/__schemas__/bigint.ts | 148 -------- .../src/__schemas__/boolean.ts | 83 ----- .../schema-generator/src/__schemas__/eq.ts | 122 ------- .../src/__schemas__/integer.ts | 169 --------- .../src/__schemas__/intersect.ts | 147 -------- .../schema-generator/src/__schemas__/never.ts | 80 ----- .../schema-generator/src/__schemas__/null.ts | 86 ----- .../src/__schemas__/number.ts | 207 ----------- .../src/__schemas__/object.ts | 330 ------------------ .../schema-generator/src/__schemas__/of.ts | 94 ----- .../src/__schemas__/optional.ts | 135 ------- .../src/__schemas__/record.ts | 160 --------- .../src/__schemas__/string.ts | 170 --------- .../src/__schemas__/symbol.ts | 83 ----- .../schema-generator/src/__schemas__/tuple.ts | 225 ------------ .../src/__schemas__/undefined.ts | 83 ----- .../schema-generator/src/__schemas__/union.ts | 144 -------- .../src/__schemas__/unknown.ts | 80 ----- .../schema-generator/src/__schemas__/void.ts | 85 ----- .../schema-generator/src/temp/any/core.ts | 36 -- .../schema-generator/src/temp/any/equals.ts | 7 - .../src/temp/any/extension.ts | 21 -- .../src/temp/any/toJsonSchema.ts | 5 - .../schema-generator/src/temp/any/toString.ts | 2 - .../schema-generator/src/temp/any/validate.ts | 10 - .../schema-generator/src/temp/array/core.ts | 128 ------- .../schema-generator/src/temp/array/equals.ts | 24 -- .../src/temp/array/extension.ts | 20 -- .../src/temp/array/toJsonSchema.ts | 36 -- .../src/temp/array/toString.ts | 22 -- .../src/temp/array/validate.ts | 27 -- .../schema-generator/src/temp/bigint/core.ts | 99 ------ .../src/temp/bigint/equals.ts | 7 - .../src/temp/bigint/extension.ts | 21 -- .../src/temp/bigint/toJsonSchema.ts | 7 - .../src/temp/bigint/toString.ts | 2 - .../src/temp/bigint/validate.ts | 13 - .../schema-generator/src/temp/boolean/core.ts | 37 -- .../src/temp/boolean/equals.ts | 7 - .../src/temp/boolean/extension.ts | 21 -- .../src/temp/boolean/toJsonSchema.ts | 5 - .../src/temp/boolean/toString.ts | 2 - .../src/temp/boolean/validate.ts | 12 - packages/schema-generator/src/temp/eq/core.ts | 41 --- .../schema-generator/src/temp/eq/equals.ts | 11 - .../schema-generator/src/temp/eq/extension.ts | 20 -- .../src/temp/eq/toJsonSchema.ts | 8 - .../schema-generator/src/temp/eq/toString.ts | 17 - .../schema-generator/src/temp/eq/validate.ts | 17 - .../schema-generator/src/temp/integer/core.ts | 100 ------ .../src/temp/integer/equals.ts | 7 - .../src/temp/integer/extension.ts | 21 -- .../src/temp/integer/toJsonSchema.ts | 23 -- .../src/temp/integer/toString.ts | 2 - .../src/temp/integer/validate.ts | 13 - .../src/temp/intersect/core.ts | 50 --- .../src/temp/intersect/equals.ts | 16 - .../src/temp/intersect/extension.ts | 20 -- .../src/temp/intersect/toJsonSchema.ts | 20 -- .../src/temp/intersect/toString.ts | 18 - .../src/temp/intersect/validate.ts | 21 -- .../schema-generator/src/temp/never/core.ts | 37 -- .../schema-generator/src/temp/never/equals.ts | 6 - .../src/temp/never/extension.ts | 21 -- .../src/temp/never/toJsonSchema.ts | 5 - .../src/temp/never/toString.ts | 2 - .../src/temp/never/validate.ts | 10 - .../schema-generator/src/temp/null/core.ts | 39 --- .../schema-generator/src/temp/null/equals.ts | 7 - .../src/temp/null/extension.ts | 21 -- .../src/temp/null/toJsonSchema.ts | 5 - .../src/temp/null/toString.ts | 2 - .../src/temp/null/validate.ts | 13 - .../schema-generator/src/temp/number/core.ts | 139 -------- .../src/temp/number/equals.ts | 7 - .../src/temp/number/extension.ts | 21 -- .../src/temp/number/toJsonSchema.ts | 23 -- .../src/temp/number/toString.ts | 2 - .../src/temp/number/validate.ts | 13 - .../schema-generator/src/temp/object/core.ts | 76 ---- .../src/temp/object/equals.ts | 55 --- .../src/temp/object/extension.ts | 20 -- .../src/temp/object/toJsonSchema.ts | 27 -- .../src/temp/object/toString.ts | 42 --- .../src/temp/object/validate.ts | 110 ------ packages/schema-generator/src/temp/of/core.ts | 47 --- .../schema-generator/src/temp/of/equals.ts | 6 - .../schema-generator/src/temp/of/extension.ts | 21 -- .../src/temp/of/toJsonSchema.ts | 7 - .../schema-generator/src/temp/of/toString.ts | 2 - .../schema-generator/src/temp/of/validate.ts | 14 - .../src/temp/optional/core.ts | 55 --- .../src/temp/optional/equals.ts | 13 - .../src/temp/optional/extension.ts | 21 -- .../src/temp/optional/toJsonSchema.ts | 19 - .../src/temp/optional/toString.ts | 15 - .../src/temp/optional/validate.ts | 17 - .../schema-generator/src/temp/record/core.ts | 50 --- .../src/temp/record/equals.ts | 32 -- .../src/temp/record/extension.ts | 20 -- .../src/temp/record/toJsonSchema.ts | 21 -- .../src/temp/record/toString.ts | 17 - .../src/temp/record/validate.ts | 24 -- .../schema-generator/src/temp/string/core.ts | 102 ------ .../src/temp/string/equals.ts | 6 - .../src/temp/string/extension.ts | 21 -- .../src/temp/string/toJsonSchema.ts | 22 -- .../src/temp/string/toString.ts | 2 - .../src/temp/string/validate.ts | 13 - .../schema-generator/src/temp/symbol/core.ts | 36 -- .../src/temp/symbol/equals.ts | 7 - .../src/temp/symbol/extension.ts | 21 -- .../src/temp/symbol/toJsonSchema.ts | 5 - .../src/temp/symbol/toString.ts | 2 - .../src/temp/symbol/validate.ts | 13 - .../schema-generator/src/temp/tuple/core.ts | 86 ----- .../schema-generator/src/temp/tuple/equals.ts | 27 -- .../src/temp/tuple/extension.ts | 20 -- .../src/temp/tuple/toJsonSchema.ts | 37 -- .../src/temp/tuple/toString.ts | 27 -- .../src/temp/tuple/validate.ts | 35 -- .../src/temp/undefined/core.ts | 36 -- .../src/temp/undefined/equals.ts | 7 - .../src/temp/undefined/extension.ts | 21 -- .../src/temp/undefined/toJsonSchema.ts | 5 - .../src/temp/undefined/toString.ts | 2 - .../src/temp/undefined/validate.ts | 13 - .../schema-generator/src/temp/union/core.ts | 50 --- .../schema-generator/src/temp/union/equals.ts | 16 - .../src/temp/union/extension.ts | 20 -- .../src/temp/union/toJsonSchema.ts | 17 - .../src/temp/union/toString.ts | 18 - .../src/temp/union/validate.ts | 26 -- .../schema-generator/src/temp/unknown/core.ts | 36 -- .../src/temp/unknown/equals.ts | 7 - .../src/temp/unknown/extension.ts | 21 -- .../src/temp/unknown/toJsonSchema.ts | 5 - .../src/temp/unknown/toString.ts | 2 - .../src/temp/unknown/validate.ts | 10 - .../schema-generator/src/temp/void/core.ts | 36 -- .../schema-generator/src/temp/void/equals.ts | 7 - .../src/temp/void/extension.ts | 21 -- .../src/temp/void/toJsonSchema.ts | 7 - .../src/temp/void/toString.ts | 2 - .../src/temp/void/validate.ts | 13 - .../schema-generator/test/generate.test.ts | 2 +- .../test/test-data/object/equals.ts | 2 +- .../test/test-data/object/validate.ts | 2 +- packages/schema-generator/tsconfig.test.json | 1 + packages/schema/src/__schemas__/any.ts | 80 ----- packages/schema/src/__schemas__/array.ts | 250 ------------- packages/schema/src/__schemas__/bigint.ts | 148 -------- packages/schema/src/__schemas__/boolean.ts | 83 ----- packages/schema/src/__schemas__/eq.ts | 122 ------- packages/schema/src/__schemas__/integer.ts | 169 --------- packages/schema/src/__schemas__/intersect.ts | 147 -------- packages/schema/src/__schemas__/never.ts | 80 ----- packages/schema/src/__schemas__/null.ts | 86 ----- packages/schema/src/__schemas__/number.ts | 207 ----------- packages/schema/src/__schemas__/object.ts | 303 ---------------- packages/schema/src/__schemas__/of.ts | 94 ----- packages/schema/src/__schemas__/optional.ts | 135 ------- packages/schema/src/__schemas__/record.ts | 160 --------- packages/schema/src/__schemas__/string.ts | 170 --------- packages/schema/src/__schemas__/symbol.ts | 83 ----- packages/schema/src/__schemas__/tuple.ts | 225 ------------ packages/schema/src/__schemas__/undefined.ts | 83 ----- packages/schema/src/__schemas__/union.ts | 144 -------- packages/schema/src/__schemas__/unknown.ts | 80 ----- packages/schema/src/__schemas__/void.ts | 85 ----- 176 files changed, 26 insertions(+), 8887 deletions(-) delete mode 100644 packages/schema-generator/src/__schemas__/any.ts delete mode 100644 packages/schema-generator/src/__schemas__/array.ts delete mode 100644 packages/schema-generator/src/__schemas__/bigint.ts delete mode 100644 packages/schema-generator/src/__schemas__/boolean.ts delete mode 100644 packages/schema-generator/src/__schemas__/eq.ts delete mode 100644 packages/schema-generator/src/__schemas__/integer.ts delete mode 100644 packages/schema-generator/src/__schemas__/intersect.ts delete mode 100644 packages/schema-generator/src/__schemas__/never.ts delete mode 100644 packages/schema-generator/src/__schemas__/null.ts delete mode 100644 packages/schema-generator/src/__schemas__/number.ts delete mode 100644 packages/schema-generator/src/__schemas__/object.ts delete mode 100644 packages/schema-generator/src/__schemas__/of.ts delete mode 100644 packages/schema-generator/src/__schemas__/optional.ts delete mode 100644 packages/schema-generator/src/__schemas__/record.ts delete mode 100644 packages/schema-generator/src/__schemas__/string.ts delete mode 100644 packages/schema-generator/src/__schemas__/symbol.ts delete mode 100644 packages/schema-generator/src/__schemas__/tuple.ts delete mode 100644 packages/schema-generator/src/__schemas__/undefined.ts delete mode 100644 packages/schema-generator/src/__schemas__/union.ts delete mode 100644 packages/schema-generator/src/__schemas__/unknown.ts delete mode 100644 packages/schema-generator/src/__schemas__/void.ts delete mode 100644 packages/schema-generator/src/temp/any/core.ts delete mode 100644 packages/schema-generator/src/temp/any/equals.ts delete mode 100644 packages/schema-generator/src/temp/any/extension.ts delete mode 100644 packages/schema-generator/src/temp/any/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/any/toString.ts delete mode 100644 packages/schema-generator/src/temp/any/validate.ts delete mode 100644 packages/schema-generator/src/temp/array/core.ts delete mode 100644 packages/schema-generator/src/temp/array/equals.ts delete mode 100644 packages/schema-generator/src/temp/array/extension.ts delete mode 100644 packages/schema-generator/src/temp/array/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/array/toString.ts delete mode 100644 packages/schema-generator/src/temp/array/validate.ts delete mode 100644 packages/schema-generator/src/temp/bigint/core.ts delete mode 100644 packages/schema-generator/src/temp/bigint/equals.ts delete mode 100644 packages/schema-generator/src/temp/bigint/extension.ts delete mode 100644 packages/schema-generator/src/temp/bigint/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/bigint/toString.ts delete mode 100644 packages/schema-generator/src/temp/bigint/validate.ts delete mode 100644 packages/schema-generator/src/temp/boolean/core.ts delete mode 100644 packages/schema-generator/src/temp/boolean/equals.ts delete mode 100644 packages/schema-generator/src/temp/boolean/extension.ts delete mode 100644 packages/schema-generator/src/temp/boolean/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/boolean/toString.ts delete mode 100644 packages/schema-generator/src/temp/boolean/validate.ts delete mode 100644 packages/schema-generator/src/temp/eq/core.ts delete mode 100644 packages/schema-generator/src/temp/eq/equals.ts delete mode 100644 packages/schema-generator/src/temp/eq/extension.ts delete mode 100644 packages/schema-generator/src/temp/eq/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/eq/toString.ts delete mode 100644 packages/schema-generator/src/temp/eq/validate.ts delete mode 100644 packages/schema-generator/src/temp/integer/core.ts delete mode 100644 packages/schema-generator/src/temp/integer/equals.ts delete mode 100644 packages/schema-generator/src/temp/integer/extension.ts delete mode 100644 packages/schema-generator/src/temp/integer/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/integer/toString.ts delete mode 100644 packages/schema-generator/src/temp/integer/validate.ts delete mode 100644 packages/schema-generator/src/temp/intersect/core.ts delete mode 100644 packages/schema-generator/src/temp/intersect/equals.ts delete mode 100644 packages/schema-generator/src/temp/intersect/extension.ts delete mode 100644 packages/schema-generator/src/temp/intersect/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/intersect/toString.ts delete mode 100644 packages/schema-generator/src/temp/intersect/validate.ts delete mode 100644 packages/schema-generator/src/temp/never/core.ts delete mode 100644 packages/schema-generator/src/temp/never/equals.ts delete mode 100644 packages/schema-generator/src/temp/never/extension.ts delete mode 100644 packages/schema-generator/src/temp/never/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/never/toString.ts delete mode 100644 packages/schema-generator/src/temp/never/validate.ts delete mode 100644 packages/schema-generator/src/temp/null/core.ts delete mode 100644 packages/schema-generator/src/temp/null/equals.ts delete mode 100644 packages/schema-generator/src/temp/null/extension.ts delete mode 100644 packages/schema-generator/src/temp/null/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/null/toString.ts delete mode 100644 packages/schema-generator/src/temp/null/validate.ts delete mode 100644 packages/schema-generator/src/temp/number/core.ts delete mode 100644 packages/schema-generator/src/temp/number/equals.ts delete mode 100644 packages/schema-generator/src/temp/number/extension.ts delete mode 100644 packages/schema-generator/src/temp/number/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/number/toString.ts delete mode 100644 packages/schema-generator/src/temp/number/validate.ts delete mode 100644 packages/schema-generator/src/temp/object/core.ts delete mode 100644 packages/schema-generator/src/temp/object/equals.ts delete mode 100644 packages/schema-generator/src/temp/object/extension.ts delete mode 100644 packages/schema-generator/src/temp/object/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/object/toString.ts delete mode 100644 packages/schema-generator/src/temp/object/validate.ts delete mode 100644 packages/schema-generator/src/temp/of/core.ts delete mode 100644 packages/schema-generator/src/temp/of/equals.ts delete mode 100644 packages/schema-generator/src/temp/of/extension.ts delete mode 100644 packages/schema-generator/src/temp/of/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/of/toString.ts delete mode 100644 packages/schema-generator/src/temp/of/validate.ts delete mode 100644 packages/schema-generator/src/temp/optional/core.ts delete mode 100644 packages/schema-generator/src/temp/optional/equals.ts delete mode 100644 packages/schema-generator/src/temp/optional/extension.ts delete mode 100644 packages/schema-generator/src/temp/optional/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/optional/toString.ts delete mode 100644 packages/schema-generator/src/temp/optional/validate.ts delete mode 100644 packages/schema-generator/src/temp/record/core.ts delete mode 100644 packages/schema-generator/src/temp/record/equals.ts delete mode 100644 packages/schema-generator/src/temp/record/extension.ts delete mode 100644 packages/schema-generator/src/temp/record/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/record/toString.ts delete mode 100644 packages/schema-generator/src/temp/record/validate.ts delete mode 100644 packages/schema-generator/src/temp/string/core.ts delete mode 100644 packages/schema-generator/src/temp/string/equals.ts delete mode 100644 packages/schema-generator/src/temp/string/extension.ts delete mode 100644 packages/schema-generator/src/temp/string/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/string/toString.ts delete mode 100644 packages/schema-generator/src/temp/string/validate.ts delete mode 100644 packages/schema-generator/src/temp/symbol/core.ts delete mode 100644 packages/schema-generator/src/temp/symbol/equals.ts delete mode 100644 packages/schema-generator/src/temp/symbol/extension.ts delete mode 100644 packages/schema-generator/src/temp/symbol/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/symbol/toString.ts delete mode 100644 packages/schema-generator/src/temp/symbol/validate.ts delete mode 100644 packages/schema-generator/src/temp/tuple/core.ts delete mode 100644 packages/schema-generator/src/temp/tuple/equals.ts delete mode 100644 packages/schema-generator/src/temp/tuple/extension.ts delete mode 100644 packages/schema-generator/src/temp/tuple/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/tuple/toString.ts delete mode 100644 packages/schema-generator/src/temp/tuple/validate.ts delete mode 100644 packages/schema-generator/src/temp/undefined/core.ts delete mode 100644 packages/schema-generator/src/temp/undefined/equals.ts delete mode 100644 packages/schema-generator/src/temp/undefined/extension.ts delete mode 100644 packages/schema-generator/src/temp/undefined/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/undefined/toString.ts delete mode 100644 packages/schema-generator/src/temp/undefined/validate.ts delete mode 100644 packages/schema-generator/src/temp/union/core.ts delete mode 100644 packages/schema-generator/src/temp/union/equals.ts delete mode 100644 packages/schema-generator/src/temp/union/extension.ts delete mode 100644 packages/schema-generator/src/temp/union/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/union/toString.ts delete mode 100644 packages/schema-generator/src/temp/union/validate.ts delete mode 100644 packages/schema-generator/src/temp/unknown/core.ts delete mode 100644 packages/schema-generator/src/temp/unknown/equals.ts delete mode 100644 packages/schema-generator/src/temp/unknown/extension.ts delete mode 100644 packages/schema-generator/src/temp/unknown/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/unknown/toString.ts delete mode 100644 packages/schema-generator/src/temp/unknown/validate.ts delete mode 100644 packages/schema-generator/src/temp/void/core.ts delete mode 100644 packages/schema-generator/src/temp/void/equals.ts delete mode 100644 packages/schema-generator/src/temp/void/extension.ts delete mode 100644 packages/schema-generator/src/temp/void/toJsonSchema.ts delete mode 100644 packages/schema-generator/src/temp/void/toString.ts delete mode 100644 packages/schema-generator/src/temp/void/validate.ts delete mode 100644 packages/schema/src/__schemas__/any.ts delete mode 100644 packages/schema/src/__schemas__/array.ts delete mode 100644 packages/schema/src/__schemas__/bigint.ts delete mode 100644 packages/schema/src/__schemas__/boolean.ts delete mode 100644 packages/schema/src/__schemas__/eq.ts delete mode 100644 packages/schema/src/__schemas__/integer.ts delete mode 100644 packages/schema/src/__schemas__/intersect.ts delete mode 100644 packages/schema/src/__schemas__/never.ts delete mode 100644 packages/schema/src/__schemas__/null.ts delete mode 100644 packages/schema/src/__schemas__/number.ts delete mode 100644 packages/schema/src/__schemas__/object.ts delete mode 100644 packages/schema/src/__schemas__/of.ts delete mode 100644 packages/schema/src/__schemas__/optional.ts delete mode 100644 packages/schema/src/__schemas__/record.ts delete mode 100644 packages/schema/src/__schemas__/string.ts delete mode 100644 packages/schema/src/__schemas__/symbol.ts delete mode 100644 packages/schema/src/__schemas__/tuple.ts delete mode 100644 packages/schema/src/__schemas__/undefined.ts delete mode 100644 packages/schema/src/__schemas__/union.ts delete mode 100644 packages/schema/src/__schemas__/unknown.ts delete mode 100644 packages/schema/src/__schemas__/void.ts diff --git a/README.md b/README.md index f5db2243..8154c30a 100644 --- a/README.md +++ b/README.md @@ -392,8 +392,6 @@ flowchart TD derive-validators(derive-validators) -.-> json(json) derive-validators(derive-validators) -.-> registry(registry) derive-validators(derive-validators) -.-> schema-core(schema-core) - schema-generator(schema-generator) -.-> registry(registry) - schema-generator(schema-generator) -.-> schema-core(schema-core) schema-seed(schema-seed) -.-> json(json) schema-seed(schema-seed) -.-> registry(registry) schema-seed(schema-seed) -.-> schema-core(schema-core) @@ -405,6 +403,12 @@ flowchart TD schema-valibot-adapter(schema-valibot-adapter) -.-> registry(registry) schema-zod-adapter(schema-zod-adapter) -.-> json(json) schema-zod-adapter(schema-zod-adapter) -.-> registry(registry) + schema-generator(schema-generator) -.-> derive-validators(derive-validators) + schema-generator(schema-generator) -.-> derive-equals(derive-equals) + schema-generator(schema-generator) -.-> registry(registry) + schema-generator(schema-generator) -.-> schema-core(schema-core) + schema-generator(schema-generator) -.-> schema-to-json-schema(schema-to-json-schema) + schema-generator(schema-generator) -.-> schema-to-string(schema-to-string) schema(schema) -.-> derive-codec(derive-codec) schema(schema) -.-> derive-equals(derive-equals) schema(schema) -.-> derive-validators(derive-validators) diff --git a/examples/sandbox/src/lib/react.ts b/examples/sandbox/src/lib/react.ts index ca238b12..8618b235 100644 --- a/examples/sandbox/src/lib/react.ts +++ b/examples/sandbox/src/lib/react.ts @@ -18,7 +18,7 @@ export function ElementSchema

(propsSch props: t.object

key: Key }> -export function ElementSchema(propsSchema: { [x: string]: t.Schema } = {}) { +export function ElementSchema

(propsSchema: P = {} as never) { return t.object({ type: t.any, props: t.object(propsSchema), diff --git a/packages/schema-generator/package.json b/packages/schema-generator/package.json index aa5f75fa..8762e45b 100644 --- a/packages/schema-generator/package.json +++ b/packages/schema-generator/package.json @@ -38,13 +38,14 @@ "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", "build:esm": "tsc -b tsconfig.build.json", - "build:schemas": "pnpm dlx tsx ./src/build.ts", - "build:schemas:watch": "pnpm dlx tsx --watch ./src/build.ts", + "gen": "pnpm dlx tsx ./src/build.ts", + "gen:w": "pnpm dlx tsx --watch ./src/build.ts", "check": "tsc -b tsconfig.json", "clean": "pnpm run \"/^clean:.*/\"", "clean:build": "rm -rf .tsbuildinfo dist build", "clean:deps": "rm -rf node_modules", - "postinstall": "pnpm dlx tsx ./src/build.ts", + "clean:gen": "rm -rf src/__schemas__ src/temp test/__generated__", + "_postinstall": "pnpm dlx tsx ./src/build.ts", "test": "vitest" }, "peerDependencies": { diff --git a/packages/schema-generator/src/__generated__/__manifest__.ts b/packages/schema-generator/src/__generated__/__manifest__.ts index 541a8247..d05c6d42 100644 --- a/packages/schema-generator/src/__generated__/__manifest__.ts +++ b/packages/schema-generator/src/__generated__/__manifest__.ts @@ -32,22 +32,30 @@ export default { "bench": "echo NOTHING TO BENCH", "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", - "build:esm": "tsc -b tsconfig.build.json", "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "gen": "pnpm dlx tsx ./src/build.ts", + "gen:w": "pnpm dlx tsx --watch ./src/build.ts", "check": "tsc -b tsconfig.json", "clean": "pnpm run \"/^clean:.*/\"", "clean:build": "rm -rf .tsbuildinfo dist build", "clean:deps": "rm -rf node_modules", - "_postinstall": "pnpm dlx tsx ./src/cli.ts", + "clean:gen": "rm -rf src/__schemas__ src/temp test/__generated__", + "_postinstall": "pnpm dlx tsx ./src/build.ts", "test": "vitest" }, "peerDependencies": { + "@traversable/derive-validators": "workspace:^", + "@traversable/derive-equals": "workspace:^", "@traversable/registry": "workspace:^", - "@traversable/schema-core": "workspace:^" + "@traversable/schema-core": "workspace:^", + "@traversable/schema-to-json-schema": "workspace:^", + "@traversable/schema-to-string": "workspace:^" }, "devDependencies": { "@clack/prompts": "^0.10.1", "@traversable/derive-validators": "workspace:^", + "@traversable/derive-equals": "workspace:^", "@traversable/registry": "workspace:^", "@traversable/schema-core": "workspace:^", "@traversable/schema-to-json-schema": "workspace:^", diff --git a/packages/schema-generator/src/__schemas__/any.ts b/packages/schema-generator/src/__schemas__/any.ts deleted file mode 100644 index 7ada302b..00000000 --- a/packages/schema-generator/src/__schemas__/any.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * any_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: unknown, right: unknown): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } -export function toJsonSchema(): toJsonSchema { - function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } - return unknownToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'any' } -export function toString(): 'any' { return 'any' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(_?: any_): validate { - validateAny.tag = URI.any - function validateAny() { return true as const } - return validateAny -} -/// validate /// -////////////////////// - -export { any_ as any } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface any_ extends any_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function AnySchema(src: unknown): src is any { return true } -AnySchema.tag = URI.any -AnySchema.def = void 0 as any - -const any_ = Object_assign( - AnySchema, - userDefinitions, -) as any_ - -Object_assign(any_, userExtensions) - -declare namespace any_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.any - _type: any - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/__schemas__/array.ts b/packages/schema-generator/src/__schemas__/array.ts deleted file mode 100644 index eed1464d..00000000 --- a/packages/schema-generator/src/__schemas__/array.ts +++ /dev/null @@ -1,250 +0,0 @@ -/** - * array schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type * as T from '@traversable/registry' -import type { - Bounds, - Equal, - Integer, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - array as arrayOf, - Array_isArray, - bindUserExtensions, - carryover, - has, - Math_max, - Math_min, - Number_isSafeInteger, - Object_assign, - Object_is, - URI, - within -} from '@traversable/registry' -import type { Guarded, Schema, SchemaLike } from '../_namespace.js' -import type { of } from './of.js' -import type { t } from '../_exports.js' -import type { SizeBounds } from '@traversable/schema-to-json-schema' -import { hasSchema } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { Errors, NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | Equal - -export function equals(arraySchema: array): equals -export function equals(arraySchema: array): equals -export function equals({ def }: array<{ equals: Equal }>): Equal { - let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is - function arrayEquals(l: unknown[], r: unknown[]): boolean { - if (Object_is(l, r)) return true - if (Array_isArray(l)) { - if (!Array_isArray(r)) return false - let len = l.length - if (len !== r.length) return false - for (let ix = len; ix-- !== 0;) - if (!equals(l[ix], r[ix])) return false - return true - } else return false - } - return arrayEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): never | T.Force< - & { type: 'array', items: T.Returns } - & T.PickIfDefined - > -} - -export function toJsonSchema>(arraySchema: T): toJsonSchema -export function toJsonSchema(arraySchema: T): toJsonSchema -export function toJsonSchema( - { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, -): () => { - type: 'array' - items: unknown - minLength?: number - maxLength?: number -} { - function arrayToJsonSchema() { - let items = hasSchema(def) ? def.toJsonSchema() : def - let out = { - type: 'array' as const, - items, - minLength, - maxLength, - } - if (typeof minLength !== 'number') delete out.minLength - if (typeof maxLength !== 'number') delete out.maxLength - return out - } - return arrayToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - /* @ts-expect-error */ - (): never | `(${ReturnType})[]` -} - -export function toString(arraySchema: array): toString -export function toString(arraySchema: array): toString -export function toString({ def }: { def: unknown }) { - function arrayToString() { - let body = ( - !!def - && typeof def === 'object' - && 'toString' in def - && typeof def.toString === 'function' - ) ? def.toString() - : '${string}' - return ('(' + body + ')[]') - } - return arrayToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = never | ValidationFn -export function validate(arraySchema: array): validate -export function validate(arraySchema: array): validate -export function validate( - { def: { validate = () => true }, minLength, maxLength }: array -) { - validateArray.tag = URI.array - function validateArray(u: unknown, path = Array.of()) { - if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] - let errors = Array.of() - if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) - if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) - for (let i = 0, len = u.length; i < len; i++) { - let y = u[i] - let results = validate(y, [...path, i]) - if (results === true) continue - else errors.push(...results) - } - return errors.length === 0 || errors - } - return validateArray -} -/// validate /// -////////////////////// - -/** @internal */ -function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { - return Object_assign(function BoundedArraySchema(u: unknown) { - return Array_isArray(u) && within(bounds)(u.length) - }, carry, array(schema)) -} - -export interface array extends array.core { - toString: toString - equals: equals - toJsonSchema: toJsonSchema - validate: validate -} - -export function array(schema: S, readonly: 'readonly'): readonlyArray -export function array(schema: S): array -export function array(schema: S): array>> -export function array(schema: S): array { - return array.def(schema) -} - -export namespace array { - export let userDefinitions: Record = { - } as array - export function def(x: S, prev?: array): array - export function def(x: S, prev?: unknown): array - export function def(x: S, prev?: array): array - export function def(x: unknown, prev?: unknown): {} { - let userExtensions: Record = { - toJsonSchema, - validate, - toString, - equals, - } - const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray - function ArraySchema(src: unknown) { return predicate(src) } - ArraySchema.tag = URI.array - ArraySchema.def = x - ArraySchema.min = function arrayMin(minLength: Min) { - return Object_assign( - boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), - { minLength }, - ) - } - ArraySchema.max = function arrayMax(maxLength: Max) { - return Object_assign( - boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), - { maxLength }, - ) - } - ArraySchema.between = function arrayBetween( - min: Min, - max: Max, - minLength = Math_min(min, max), - maxLength = Math_max(min, max) - ) { - return Object_assign( - boundedArray(x, { gte: minLength, lte: maxLength }), - { minLength, maxLength }, - ) - } - if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength - if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength - Object_assign(ArraySchema, userDefinitions) - return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) - } -} - -export declare namespace array { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.array - get def(): S - _type: S['_type' & keyof S][] - minLength?: number - maxLength?: number - min>(minLength: Min): array.Min - max>(maxLength: Max): array.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> - } - type Min - = [Self] extends [{ maxLength: number }] - ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> - : array.min - ; - type Max - = [Self] extends [{ minLength: number }] - ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> - : array.max - ; - interface min extends array { minLength: Min } - interface max extends array { maxLength: Max } - interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } - type type = never | T -} - -export const readonlyArray: { - (schema: S): readonlyArray - (schema: S): readonlyArray> -} = array -export interface readonlyArray { - (u: unknown): u is this['_type'] - tag: URI.array - def: S - _type: ReadonlyArray -} diff --git a/packages/schema-generator/src/__schemas__/bigint.ts b/packages/schema-generator/src/__schemas__/bigint.ts deleted file mode 100644 index bb4b337e..00000000 --- a/packages/schema-generator/src/__schemas__/bigint.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * bigint_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Bounds, Equal, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Object_assign, - Object_is, - URI, - withinBig as within -} from '@traversable/registry' -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' -import type { t } from '../_exports.js' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: bigint, right: bigint): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function bigintToJsonSchema(): void { - return void 0 - } - return bigintToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'bigint' } -export function toString(): 'bigint' { return 'bigint' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(bigIntSchema: S): validate { - validateBigInt.tag = URI.bigint - function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { - return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] - } - return validateBigInt -} -/// validate /// -////////////////////// - -export { bigint_ as bigint } - -/** @internal */ -function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ -function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ -function boundedBigInt(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedBigIntSchema(u: unknown) { - return bigint_(u) && within(bounds)(u) - }, carry, bigint_) -} - -interface bigint_ extends bigint_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -function BigIntSchema(src: unknown) { return typeof src === 'bigint' } -BigIntSchema.tag = URI.bigint -BigIntSchema.def = 0n - -const bigint_ = Object_assign( - BigIntSchema, - userDefinitions, -) as bigint_ - -bigint_.min = function bigIntMin(minimum) { - return Object_assign( - boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -bigint_.max = function bigIntMax(maximum) { - return Object_assign( - boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -bigint_.between = function bigIntBetween( - min, - max, - minimum = (max < min ? max : min), - maximum = (max < min ? min : max), -) { - return Object_assign( - boundedBigInt({ gte: minimum, lte: maximum }), - { minimum, maximum } - ) -} - -Object_assign( - bigint_, - bindUserExtensions(bigint_, userExtensions), -) - -declare namespace bigint_ { - interface core extends bigint_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: bigint - tag: URI.bigint - get def(): this['_type'] - minimum?: bigint - maximum?: bigint - } - type Min - = [Self] extends [{ maximum: bigint }] - ? bigint_.between<[min: X, max: Self['maximum']]> - : bigint_.min - ; - type Max - = [Self] extends [{ minimum: bigint }] - ? bigint_.between<[min: Self['minimum'], max: X]> - : bigint_.max - ; - interface methods { - min(minimum: Min): bigint_.Min - max(maximum: Max): bigint_.Max - between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> - } - interface min extends bigint_ { minimum: Min } - interface max extends bigint_ { maximum: Max } - interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } -} diff --git a/packages/schema-generator/src/__schemas__/boolean.ts b/packages/schema-generator/src/__schemas__/boolean.ts deleted file mode 100644 index ebbaac60..00000000 --- a/packages/schema-generator/src/__schemas__/boolean.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * boolean_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: boolean, right: boolean): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { type: 'boolean' } } -export function toJsonSchema(): toJsonSchema { - function booleanToJsonSchema() { return { type: 'boolean' as const } } - return booleanToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'boolean' } -export function toString(): 'boolean' { return 'boolean' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(booleanSchema: boolean_): validate { - validateBoolean.tag = URI.boolean - function validateBoolean(u: unknown, path = Array.of()) { - return booleanSchema(true as const) || [NullaryErrors.null(u, path)] - } - return validateBoolean -} -/// validate /// -////////////////////// - -export { boolean_ as boolean } - -interface boolean_ extends boolean_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } - -BooleanSchema.tag = URI.boolean -BooleanSchema.def = false - -const boolean_ = Object_assign( - BooleanSchema, - userDefinitions, -) as boolean_ - -Object_assign(boolean_, userExtensions) - -declare namespace boolean_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.boolean - _type: boolean - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/__schemas__/eq.ts b/packages/schema-generator/src/__schemas__/eq.ts deleted file mode 100644 index eb99ab5f..00000000 --- a/packages/schema-generator/src/__schemas__/eq.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * eq schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Key, - Mut, - Mutable, - SchemaOptions as Options, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - applyOptions, - bindUserExtensions, - Equal, - getConfig, - laxEquals, - Object_assign, - URI -} from '@traversable/registry' -import type { t } from '../_exports.js' -import { stringify } from '@traversable/schema-to-string' -import type { Validate } from '@traversable/derive-validators' -import { Errors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | Equal -export function equals(eqSchema: eq): equals -export function equals(): Equal { - return function eqEquals(left: any, right: any) { - return laxEquals(left, right) - } -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { const: T } } -export function toJsonSchema(eqSchema: eq): toJsonSchema -export function toJsonSchema({ def }: eq) { - function eqToJsonSchema() { return { const: def } } - return eqToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - (): [Key] extends [never] - ? [T] extends [symbol] ? 'symbol' : 'symbol' - : [T] extends [string] ? `'${T}'` : Key -} - -export function toString(eqSchema: eq): toString -export function toString({ def }: eq): () => string { - function eqToString(): string { - return typeof def === 'symbol' ? 'symbol' : stringify(def) - } - return eqToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate -export function validate(eqSchema: eq): validate -export function validate({ def }: eq): validate { - validateEq.tag = URI.eq - function validateEq(u: unknown, path = Array.of()) { - let options = getConfig().schema - let equals = options?.eq?.equalsFn || Equal.lax - if (equals(def, u)) return true - else return [Errors.eq(u, path, def)] - } - return validateEq -} -/// validate /// -////////////////////// - -export function eq>(value: V, options?: Options): eq> -export function eq(value: V, options?: Options): eq -export function eq(value: V, options?: Options): eq { - return eq.def(value, options) -} - -export interface eq extends eq.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export namespace eq { - export let userDefinitions: Record = { - } - export function def(value: T, options?: Options): eq - export function def(x: T, $?: Options): {} { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const options = applyOptions($) - const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) - function EqSchema(src: unknown) { return predicate(src) } - EqSchema.tag = URI.eq - EqSchema.def = x - Object_assign(EqSchema, eq.userDefinitions) - return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) - } -} - -export declare namespace eq { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.eq - _type: V - get def(): V - } -} diff --git a/packages/schema-generator/src/__schemas__/integer.ts b/packages/schema-generator/src/__schemas__/integer.ts deleted file mode 100644 index 6594ba9b..00000000 --- a/packages/schema-generator/src/__schemas__/integer.ts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * integer schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Bounds, - Equal, - Force, - Integer, - PickIfDefined, - Unknown -} from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_max, - Math_min, - Number_isSafeInteger, - Object_assign, - SameValueNumber, - URI, - within -} from '@traversable/registry' -import type { t } from '../_exports.js' -import type { NumericBounds } from '@traversable/schema-to-json-schema' -import { getNumericBounds } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: number, right: number): boolean { - return SameValueNumber(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: integer): toJsonSchema { - function integerToJsonSchema() { - const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) - let bounds: NumericBounds = {} - if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum - if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum - if (typeof minimum === 'number') bounds.minimum = minimum - if (typeof maximum === 'number') bounds.maximum = maximum - return { - type: 'integer' as const, - ...bounds, - } - } - return integerToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'number' } -export function toString(): 'number' { return 'number' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(integerSchema: S): validate { - validateInteger.tag = URI.integer - function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { - return integerSchema(u) || [NullaryErrors.integer(u, path)] - } - return validateInteger -} -/// validate /// -////////////////////// - -export { integer } - -/** @internal */ -function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer -function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer -function boundedInteger(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedIntegerSchema(u: unknown) { - return integer(u) && within(bounds)(u) - }, carry, integer) -} - -interface integer extends integer.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let userDefinitions: Record = { - equals, - toString, -} - -export let userExtensions: Record = { - toJsonSchema, - validate, -} - -function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } -IntegerSchema.tag = URI.integer -IntegerSchema.def = 0 - -const integer = Object_assign( - IntegerSchema, - userDefinitions, -) as integer - -integer.min = function integerMin(minimum) { - return Object_assign( - boundedInteger({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -integer.max = function integerMax(maximum) { - return Object_assign( - boundedInteger({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -integer.between = function integerBetween( - min, - max, - minimum = Math_min(min, max), - maximum = Math_max(min, max), -) { - return Object_assign( - boundedInteger({ gte: minimum, lte: maximum }), - { minimum, maximum }, - ) -} - -Object_assign( - integer, - bindUserExtensions(integer, userExtensions), -) - -declare namespace integer { - interface core extends integer.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: number - tag: URI.integer - get def(): this['_type'] - minimum?: number - maximum?: number - } - interface methods { - min>(minimum: Min): integer.Min - max>(maximum: Max): integer.Max - between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ maximum: number }] - ? integer.between<[min: X, max: Self['maximum']]> - : integer.min - type Max - = [Self] extends [{ minimum: number }] - ? integer.between<[min: Self['minimum'], max: X]> - : integer.max - interface min extends integer { minimum: Min } - interface max extends integer { maximum: Max } - interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } -} diff --git a/packages/schema-generator/src/__schemas__/intersect.ts b/packages/schema-generator/src/__schemas__/intersect.ts deleted file mode 100644 index fbc80453..00000000 --- a/packages/schema-generator/src/__schemas__/intersect.ts +++ /dev/null @@ -1,147 +0,0 @@ -/** - * intersect schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Equal, - Join, - Returns, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - Array_isArray, - bindUserExtensions, - intersect as intersect$, - isUnknown as isAny, - Object_assign, - Object_is, - URI -} from '@traversable/registry' -import type { - Entry, - IntersectType, - Schema, - SchemaLike -} from '../_namespace.js' -import type { t } from '../_exports.js' -import { getSchema } from '@traversable/schema-to-json-schema' -import { callToString } from '@traversable/schema-to-string' -import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(intersectSchema: intersect<[...S]>): equals -export function equals(intersectSchema: intersect<[...S]>): equals -export function equals({ def }: intersect<{ equals: Equal }[]>): Equal { - function intersectEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - for (let ix = def.length; ix-- !== 0;) - if (!def[ix].equals(l, r)) return false - return true - } - return intersectEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): { - allOf: { [I in keyof T]: Returns } - } -} - -export function toJsonSchema(intersectSchema: intersect): toJsonSchema -export function toJsonSchema(intersectSchema: intersect): toJsonSchema -export function toJsonSchema({ def }: intersect): () => {} { - function intersectToJsonSchema() { - return { - allOf: def.map(getSchema) - } - } - return intersectToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - (): never | [T] extends [readonly []] ? 'unknown' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` -} - -export function toString(intersectSchema: intersect): toString -export function toString({ def }: intersect): () => string { - function intersectToString() { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' - } - return intersectToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate - -export function validate(intersectSchema: intersect): validate -export function validate(intersectSchema: intersect): validate -export function validate({ def }: intersect) { - validateIntersect.tag = URI.intersect - function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { - let errors = Array.of() - for (let i = 0; i < def.length; i++) { - let results = def[i].validate(u, path) - if (results !== true) - for (let j = 0; j < results.length; j++) errors.push(results[j]) - } - return errors.length === 0 || errors - } - return validateIntersect -} -/// validate /// -////////////////////// - -export function intersect(...schemas: S): intersect -export function intersect }>(...schemas: S): intersect -export function intersect(...schemas: readonly unknown[]) { - return intersect.def(schemas) -} - -export interface intersect extends intersect.core { - toString: toString - equals: equals - toJsonSchema: toJsonSchema - validate: validate -} - -export namespace intersect { - export let userDefinitions: Record = { - } as intersect - export function def(xs: readonly [...T]): intersect - export function def(xs: readonly unknown[]): {} { - let userExtensions: Record = { - toJsonSchema, - validate, - toString, - equals, - } - const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny - function IntersectSchema(src: unknown) { return predicate(src) } - IntersectSchema.tag = URI.intersect - IntersectSchema.def = xs - Object_assign(IntersectSchema, intersect.userDefinitions) - return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) - } -} - -export declare namespace intersect { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.intersect - get def(): S - _type: IntersectType - } - type type> = never | T -} diff --git a/packages/schema-generator/src/__schemas__/never.ts b/packages/schema-generator/src/__schemas__/never.ts deleted file mode 100644 index 25d455a1..00000000 --- a/packages/schema-generator/src/__schemas__/never.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * never_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: never, right: never): boolean { - return false -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): never } -export function toJsonSchema(): toJsonSchema { - function neverToJsonSchema() { return void 0 as never } - return neverToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'never' } -export function toString(): 'never' { return 'never' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(_?: never_): validate { - validateNever.tag = URI.never - function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } - return validateNever -} -/// validate /// -////////////////////// - -export { never_ as never } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface never_ extends never_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function NeverSchema(src: unknown): src is never { return false } -NeverSchema.tag = URI.never; -NeverSchema.def = void 0 as never - -const never_ = Object_assign( - NeverSchema, - userDefinitions, -) as never_ - -Object_assign(never_, userExtensions) - -export declare namespace never_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.never - _type: never - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/__schemas__/null.ts b/packages/schema-generator/src/__schemas__/null.ts deleted file mode 100644 index e9f0a525..00000000 --- a/packages/schema-generator/src/__schemas__/null.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * null_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: null, right: null): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { type: 'null', enum: [null] } } -export function toJsonSchema(): toJsonSchema { - function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } - return nullToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'null' } -export function toString(): 'null' { return 'null' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(nullSchema: null_): validate { - validateNull.tag = URI.null - function validateNull(u: unknown, path = Array.of()) { - return nullSchema(u) || [NullaryErrors.null(u, path)] - } - return validateNull -} -/// validate /// -////////////////////// - -export { null_ as null, null_ } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface null_ extends null_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function NullSchema(src: unknown): src is null { return src === null } -NullSchema.def = null -NullSchema.tag = URI.null - -const null_ = Object_assign( - NullSchema, - userDefinitions, -) as null_ - -Object_assign( - null_, - userExtensions, -) - -declare namespace null_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.null - _type: null - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/__schemas__/number.ts b/packages/schema-generator/src/__schemas__/number.ts deleted file mode 100644 index f079373f..00000000 --- a/packages/schema-generator/src/__schemas__/number.ts +++ /dev/null @@ -1,207 +0,0 @@ -/** - * number_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Bounds, - Equal, - Force, - PickIfDefined, - Unknown -} from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_max, - Math_min, - Object_assign, - SameValueNumber, - URI, - within -} from '@traversable/registry' -import type { t } from '../_exports.js' -import type { NumericBounds } from '@traversable/schema-to-json-schema' -import { getNumericBounds } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: number, right: number): boolean { - return SameValueNumber(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: number_): toJsonSchema { - function numberToJsonSchema() { - const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) - let bounds: NumericBounds = {} - if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum - if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum - if (typeof minimum === 'number') bounds.minimum = minimum - if (typeof maximum === 'number') bounds.maximum = maximum - return { - type: 'number' as const, - ...bounds, - } - } - return numberToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'number' } -export function toString(): 'number' { return 'number' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(numberSchema: S): validate { - validateNumber.tag = URI.number - function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { - return numberSchema(u) || [NullaryErrors.number(u, path)] - } - return validateNumber -} -/// validate /// -////////////////////// - -export { number_ as number } - -interface number_ extends number_.core { - toString: toString - equals: equals - toJsonSchema: toJsonSchema - validate: validate -} - -export let userDefinitions: Record = { - toString, - equals, -} - -export let userExtensions: Record = { - toJsonSchema, - validate, -} - -function NumberSchema(src: unknown) { return typeof src === 'number' } -NumberSchema.tag = URI.number -NumberSchema.def = 0 - -const number_ = Object_assign( - NumberSchema, - userDefinitions, -) as number_ - -number_.min = function numberMin(minimum) { - return Object_assign( - boundedNumber({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -number_.max = function numberMax(maximum) { - return Object_assign( - boundedNumber({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -number_.moreThan = function numberMoreThan(exclusiveMinimum) { - return Object_assign( - boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), - { exclusiveMinimum }, - ) -} -number_.lessThan = function numberLessThan(exclusiveMaximum) { - return Object_assign( - boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), - { exclusiveMaximum }, - ) -} -number_.between = function numberBetween( - min, - max, - minimum = Math_min(min, max), - maximum = Math_max(min, max), -) { - return Object_assign( - boundedNumber({ gte: minimum, lte: maximum }), - { minimum, maximum }, - ) -} - -Object_assign( - number_, - bindUserExtensions(number_, userExtensions), -) - -function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ -function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ -function boundedNumber(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedNumberSchema(u: unknown) { - return typeof u === 'number' && within(bounds)(u) - }, carry, number_) -} - -declare namespace number_ { - interface core extends number_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: number - tag: URI.number - get def(): this['_type'] - minimum?: number - maximum?: number - exclusiveMinimum?: number - exclusiveMaximum?: number - } - interface methods { - min(minimum: Min): number_.Min - max(maximum: Max): number_.Max - moreThan(moreThan: Min): ExclusiveMin - lessThan(lessThan: Max): ExclusiveMax - between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ exclusiveMaximum: number }] - ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> - : [Self] extends [{ maximum: number }] - ? number_.between<[min: X, max: Self['maximum']]> - : number_.min - ; - type Max - = [Self] extends [{ exclusiveMinimum: number }] - ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> - : [Self] extends [{ minimum: number }] - ? number_.between<[min: Self['minimum'], max: X]> - : number_.max - ; - type ExclusiveMin - = [Self] extends [{ exclusiveMaximum: number }] - ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> - : [Self] extends [{ maximum: number }] - ? number_.maxStrictMin<[min: X, Self['maximum']]> - : number_.moreThan - ; - type ExclusiveMax - = [Self] extends [{ exclusiveMinimum: number }] - ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> - : [Self] extends [{ minimum: number }] - ? number_.minStrictMax<[Self['minimum'], min: X]> - : number_.lessThan - ; - interface min extends number_ { minimum: Min } - interface max extends number_ { maximum: Max } - interface moreThan extends number_ { exclusiveMinimum: Min } - interface lessThan extends number_ { exclusiveMaximum: Max } - interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } - interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } - interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } - interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } -} diff --git a/packages/schema-generator/src/__schemas__/object.ts b/packages/schema-generator/src/__schemas__/object.ts deleted file mode 100644 index d85bd88b..00000000 --- a/packages/schema-generator/src/__schemas__/object.ts +++ /dev/null @@ -1,330 +0,0 @@ -/** - * object_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type * as T from '@traversable/registry' -import type { - Force, - Join, - Returns, - SchemaOptions as Options, - UnionToTuple, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - applyOptions, - Array_isArray, - bindUserExtensions, - fn, - has, - isAnyObject, - object as object$, - Object_assign, - Object_hasOwn, - Object_is, - Object_keys, - record as record$, - symbol, - typeName, - URI -} from '@traversable/registry' -import type { - Entry, - Optional, - Required, - Schema, - SchemaLike -} from '../_namespace.js' -import type { t } from '../_exports.js' -import { getConfig } from '../_exports.js' -import type { RequiredKeys } from '@traversable/schema-to-json-schema' -import { isRequired, property } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { Errors, NullaryErrors, UnaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | T.Equal -export function equals(objectSchema: object_): equals> -export function equals(objectSchema: object_): equals> -export function equals({ def }: object_): equals> { - function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { - if (Object_is(l, r)) return true - if (!l || typeof l !== 'object' || Array_isArray(l)) return false - if (!r || typeof r !== 'object' || Array_isArray(r)) return false - for (const k in def) { - const lHas = Object_hasOwn(l, k) - const rHas = Object_hasOwn(r, k) - if (lHas) { - if (!rHas) return false - if (!def[k].equals(l[k], r[k])) return false - } - if (rHas) { - if (!lHas) return false - if (!def[k].equals(l[k], r[k])) return false - } - if (!def[k].equals(l[k], r[k])) return false - } - return true - } - return objectEquals -} - -// export type equals = never | T.Equal -// export function equals(objectSchema: object_): equals> -// export function equals(objectSchema: object_): equals> -// export function equals({ def }: object_<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { -// function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { -// if (Object_is(l, r)) return true -// if (!l || typeof l !== 'object' || Array_isArray(l)) return false -// if (!r || typeof r !== 'object' || Array_isArray(r)) return false -// for (const k in def) { -// const lHas = Object_hasOwn(l, k) -// const rHas = Object_hasOwn(r, k) -// if (lHas) { -// if (!rHas) return false -// if (!def[k].equals(l[k], r[k])) return false -// } -// if (rHas) { -// if (!lHas) return false -// if (!def[k].equals(l[k], r[k])) return false -// } -// if (!def[k].equals(l[k], r[k])) return false -// } -// return true -// } -// return objectEquals -// } -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema = RequiredKeys> { - (): { - type: 'object' - required: { [I in keyof KS]: KS[I] & string } - properties: { [K in keyof T]: Returns } - } -} - -export function toJsonSchema(objectSchema: object_): toJsonSchema -export function toJsonSchema(objectSchema: object_): toJsonSchema -export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { - const required = Object_keys(def).filter(isRequired(def)) - function objectToJsonSchema() { - return { - type: 'object' as const, - required, - properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), - } - } - return objectToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -/** @internal */ -type Symbol_optional = typeof Symbol_optional -const Symbol_optional: typeof symbol.optional = symbol.optional - -/** @internal */ -const hasOptionalSymbol = (u: unknown): u is { toString(): T } => - !!u && typeof u === 'function' - && Symbol_optional in u - && typeof u[Symbol_optional] === 'number' - -/** @internal */ -const hasToString = (x: unknown): x is { toString(): string } => - !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' - -export interface toString> { - (): never - | [keyof T] extends [never] ? '{}' - /* @ts-expect-error */ - : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` -} - - -export function toString>(objectSchema: object_): toString -export function toString({ def }: object_) { - function objectToString() { - if (!!def && typeof def === 'object') { - const entries = Object.entries(def) - if (entries.length === 0) return '{}' - else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" - }: ${hasToString(x) ? x.toString() : 'unknown' - }`).join(', ') - } }` - } - else return '{ [x: string]: unknown }' - } - - return objectToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -/** @internal */ -let isObject = (u: unknown): u is { [x: string]: unknown } => - !!u && typeof u === 'object' && !Array_isArray(u) - -/** @internal */ -let isKeyOf = (k: keyof any, u: T): k is keyof T => - !!u && (typeof u === 'function' || typeof u === 'object') && k in u - -/** @internal */ -let isOptional = has('tag', (tag) => tag === URI.optional) - - -export type validate = never | ValidationFn - -export function validate(objectSchema: object_): validate -export function validate(objectSchema: object_): validate -export function validate(objectSchema: object_): validate<{ [x: string]: unknown }> { - validateObject.tag = URI.object - function validateObject(u: unknown, path_ = Array.of()) { - // if (objectSchema(u)) return true - if (!isObject(u)) return [Errors.object(u, path_)] - let errors = Array.of() - let { schema: { optionalTreatment } } = getConfig() - let keys = Object_keys(objectSchema.def) - if (optionalTreatment === 'exactOptional') { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path = [...path_, k] - if (Object_hasOwn(u, k) && u[k] === undefined) { - if (isOptional(objectSchema.def[k].validate)) { - let tag = typeName(objectSchema.def[k].validate) - if (isKeyOf(tag, NullaryErrors)) { - let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] - errors.push(NullaryErrors[tag](...args)) - } - else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) - } - } - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - let tag = typeName(objectSchema.def[k].validate) - if (isKeyOf(tag, NullaryErrors)) { - errors.push(NullaryErrors[tag](u[k], path, tag)) - } - else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag].invalid(u[k], path)) - } - errors.push(...results) - } - else if (Object_hasOwn(u, k)) { - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - errors.push(...results) - continue - } else { - errors.push(UnaryErrors.object.missing(u, path)) - continue - } - } - } - else { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path = [...path_, k] - if (!Object_hasOwn(u, k)) { - if (!isOptional(objectSchema.def[k].validate)) { - errors.push(UnaryErrors.object.missing(u, path)) - continue - } - else { - if (!Object_hasOwn(u, k)) continue - if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { - if (u[k] === undefined) continue - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - for (let j = 0; j < results.length; j++) { - let result = results[j] - errors.push(result) - continue - } - } - } - } - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - for (let l = 0; l < results.length; l++) { - let result = results[l] - errors.push(result) - } - } - } - return errors.length === 0 || errors - } - - return validateObject -} -/// validate /// -////////////////////// - -export { object_ as object } - -function object_< - S extends { [x: string]: Schema }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_< - S extends { [x: string]: SchemaLike }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_(schemas: S, options?: Options) { - return object_.def(schemas, options) -} - -interface object_ extends object_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -namespace object_ { - export let userDefinitions: Record = { - } as object_ - export function def(xs: T, $?: Options, opt?: string[]): object_ - export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const keys = Object_keys(xs) - const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) - const req = keys.filter((k) => !has(symbol.optional)(xs[k])) - const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) - function ObjectSchema(src: unknown) { return predicate(src) } - ObjectSchema.tag = URI.object - ObjectSchema.def = xs - ObjectSchema.opt = opt - ObjectSchema.req = req - Object_assign(ObjectSchema, userDefinitions) - return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) - } -} - -declare namespace object_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: object_.type - tag: URI.object - get def(): S - opt: Optional // TODO: use object_.Opt? - req: Required // TODO: use object_.Req? - } - type Opt = symbol.optional extends keyof S[K] ? never : K - type Req = symbol.optional extends keyof S[K] ? K : never - type type = Force< - & { [K in keyof S as Opt]-?: S[K]['_type' & keyof S[K]] } - & { [K in keyof S as Req]+?: S[K]['_type' & keyof S[K]] } - > -} diff --git a/packages/schema-generator/src/__schemas__/of.ts b/packages/schema-generator/src/__schemas__/of.ts deleted file mode 100644 index 94bae7ec..00000000 --- a/packages/schema-generator/src/__schemas__/of.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * of schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Unknown } from '@traversable/registry' -import { Equal, Object_assign, URI } from '@traversable/registry' -import type { - Entry, - Guard, - Guarded, - SchemaLike -} from '../_namespace.js' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: T, right: T): boolean { - return Equal.lax(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function inlineToJsonSchema(): void { - return void 0 - } - return inlineToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'unknown' } -export function toString(): 'unknown' { return 'unknown' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(inlineSchema: of): validate { - validateInline.tag = URI.inline - function validateInline(u: unknown, path = Array.of()) { - return inlineSchema(u) || [NullaryErrors.inline(u, path)] - } - return validateInline -} -/// validate /// -////////////////////// - -export interface of extends of.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export function of(typeguard: S): Entry -export function of(typeguard: S): of -export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { - typeguard.def = typeguard - return Object_assign(typeguard, of.prototype) -} - -export namespace of { - export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, - } - export let userExtensions: Record = { - validate, - } - export function def(guard: T): of - export function def(guard: T) { - function InlineSchema(src: unknown) { return guard(src) } - InlineSchema.tag = URI.inline - InlineSchema.def = guard - return InlineSchema - } -} - -export declare namespace of { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: Guarded - tag: URI.inline - get def(): S - } - type type> = never | T -} diff --git a/packages/schema-generator/src/__schemas__/optional.ts b/packages/schema-generator/src/__schemas__/optional.ts deleted file mode 100644 index 426e5843..00000000 --- a/packages/schema-generator/src/__schemas__/optional.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * optional schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Equal, - Force, - Returns, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - bindUserExtensions, - has, - isUnknown as isAny, - Object_assign, - Object_is, - optional as optional$, - symbol, - URI -} from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../_exports.js' -import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' -import { callToString } from '@traversable/schema-to-string' -import type { Validate, ValidationFn, Validator } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | Equal -export function equals(optionalSchema: optional): equals -export function equals(optionalSchema: optional): equals -export function equals({ def }: optional<{ equals: Equal }>): Equal { - return function optionalEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - return def.equals(l, r) - } -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -type Nullable = Force - -export interface toJsonSchema { - (): Nullable> - [symbol.optional]: number -} - -export function toJsonSchema(optionalSchema: optional): toJsonSchema -export function toJsonSchema({ def }: optional) { - function optionalToJsonSchema() { return getSchema(def) } - optionalToJsonSchema[symbol.optional] = wrapOptional(def) - return optionalToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - /* @ts-expect-error */ - (): never | `(${ReturnType} | undefined)` -} - -export function toString(optionalSchema: optional): toString -export function toString({ def }: optional): () => string { - function optionalToString(): string { - return '(' + callToString(def) + ' | undefined)' - } - return optionalToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate - -export function validate(optionalSchema: optional): validate -export function validate(optionalSchema: optional): validate -export function validate({ def }: optional): ValidationFn { - validateOptional.tag = URI.optional - validateOptional.optional = 1 - function validateOptional(u: unknown, path = Array.of()) { - if (u === void 0) return true - return def.validate(u, path) - } - return validateOptional -} -/// validate /// -////////////////////// - -export function optional(schema: S): optional -export function optional(schema: S): optional> -export function optional(schema: S): optional { return optional.def(schema) } - -export interface optional extends optional.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export namespace optional { - export let userDefinitions: Record = { - } - export function def(x: T): optional - export function def(x: T) { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const predicate = _isPredicate(x) ? optional$(x) : isAny - function OptionalSchema(src: unknown) { return predicate(src) } - OptionalSchema.tag = URI.optional - OptionalSchema.def = x - OptionalSchema[symbol.optional] = 1 - Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) - return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) - } - export const is - : (u: unknown) => u is optional - = has('tag', (u) => u === URI.optional) -} - -export declare namespace optional { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.optional - _type: undefined | S['_type' & keyof S] - def: S - [symbol.optional]: number - } - export type type = never | T -} diff --git a/packages/schema-generator/src/__schemas__/record.ts b/packages/schema-generator/src/__schemas__/record.ts deleted file mode 100644 index 63d12e6c..00000000 --- a/packages/schema-generator/src/__schemas__/record.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * record schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type * as T from '@traversable/registry' -import type { Equal, Returns, Unknown } from '@traversable/registry' -import { - _isPredicate, - Array_isArray, - bindUserExtensions, - isAnyObject, - Object_assign, - Object_hasOwn, - Object_is, - Object_keys, - record as record$, - URI -} from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../_exports.js' -import { getSchema } from '@traversable/schema-to-json-schema' -import { callToString } from '@traversable/schema-to-string' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | Equal -export function equals(recordSchema: record): equals -export function equals(recordSchema: record): equals -export function equals({ def }: record<{ equals: Equal }>): Equal> { - function recordEquals(l: Record, r: Record): boolean { - if (Object_is(l, r)) return true - if (!l || typeof l !== 'object' || Array_isArray(l)) return false - if (!r || typeof r !== 'object' || Array_isArray(r)) return false - const lhs = Object_keys(l) - const rhs = Object_keys(r) - let len = lhs.length - let k: string - if (len !== rhs.length) return false - for (let ix = len; ix-- !== 0;) { - k = lhs[ix] - if (!Object_hasOwn(r, k)) return false - if (!(def.equals(l[k], r[k]))) return false - } - len = rhs.length - for (let ix = len; ix-- !== 0;) { - k = rhs[ix] - if (!Object_hasOwn(l, k)) return false - if (!(def.equals(l[k], r[k]))) return false - } - return true - } - return recordEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): { - type: 'object' - additionalProperties: T.Returns - } -} - -export function toJsonSchema(recordSchema: record): toJsonSchema -export function toJsonSchema(recordSchema: record): toJsonSchema -export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { - return function recordToJsonSchema() { - return { - type: 'object' as const, - additionalProperties: getSchema(def), - } - } -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - /* @ts-expect-error */ - (): never | `Record}>` -} - -export function toString>(recordSchema: S): toString -export function toString(recordSchema: record): toString -export function toString({ def }: { def: unknown }): () => string { - function recordToString() { - return `Record` - } - return recordToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = never | ValidationFn -export function validate(recordSchema: record): validate -export function validate(recordSchema: record): validate -export function validate({ def: { validate = () => true } }: record) { - validateRecord.tag = URI.record - function validateRecord(u: unknown, path = Array.of()) { - if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] - let errors = Array.of() - let keys = Object_keys(u) - for (let k of keys) { - let y = u[k] - let results = validate(y, [...path, k]) - if (results === true) continue - else errors.push(...results) - } - return errors.length === 0 || errors - } - return validateRecord -} -/// validate /// -////////////////////// - -export function record(schema: S): record -export function record(schema: S): record> -export function record(schema: Schema) { - return record.def(schema) -} - -export interface record extends record.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export namespace record { - export let userDefinitions: Record = { - } - export function def(x: T): record - export function def(x: unknown): {} { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const predicate = _isPredicate(x) ? record$(x) : isAnyObject - function RecordSchema(src: unknown) { return predicate(src) } - RecordSchema.tag = URI.record - RecordSchema.def = x - Object_assign(RecordSchema, record.userDefinitions) - return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) - } -} - -export declare namespace record { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.record - get def(): S - _type: Record - } - export type type> = never | T -} diff --git a/packages/schema-generator/src/__schemas__/string.ts b/packages/schema-generator/src/__schemas__/string.ts deleted file mode 100644 index deb1dd42..00000000 --- a/packages/schema-generator/src/__schemas__/string.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * string_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Bounds, - Equal, - Force, - Integer, - PickIfDefined, - Unknown -} from '@traversable/registry' -import { - bindUserExtensions, - carryover, - has, - Math_max, - Math_min, - Object_assign, - URI, - within -} from '@traversable/registry' -import type { t } from '../_exports.js' -import type { SizeBounds } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: string, right: string): boolean { - return left === right -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): Force<{ type: 'string' } & PickIfDefined> -} - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: string_): () => { type: 'string' } & Partial { - function stringToJsonSchema() { - const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null - const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null - let out: { type: 'string' } & Partial = { type: 'string' } - minLength !== null && void (out.minLength = minLength) - maxLength !== null && void (out.maxLength = maxLength) - - return out - } - return stringToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'string' } -export function toString(): 'string' { return 'string' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(stringSchema: S): validate { - validateString.tag = URI.string - function validateString(u: unknown, path = Array.of()): true | ValidationError[] { - return stringSchema(u) || [NullaryErrors.number(u, path)] - } - return validateString -} -/// validate /// -////////////////////// - -export { string_ as string } - -/** @internal */ -function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ -function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ -function boundedString(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedStringSchema(u: unknown) { - return string_(u) && within(bounds)(u.length) - }, carry, string_) -} - -interface string_ extends string_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let userDefinitions: Record = { - toString, - equals, -} - -export let userExtensions: Record = { - toJsonSchema, - validate, -} - -function StringSchema(src: unknown) { return typeof src === 'string' } -StringSchema.tag = URI.string -StringSchema.def = '' - -const string_ = Object_assign( - StringSchema, - userDefinitions, -) as string_ - -string_.min = function stringMinLength(minLength) { - return Object_assign( - boundedString({ gte: minLength }, carryover(this, 'minLength')), - { minLength }, - ) -} -string_.max = function stringMaxLength(maxLength) { - return Object_assign( - boundedString({ lte: maxLength }, carryover(this, 'maxLength')), - { maxLength }, - ) -} -string_.between = function stringBetween( - min, - max, - minLength = Math_min(min, max), - maxLength = Math_max(min, max)) { - return Object_assign( - boundedString({ gte: minLength, lte: maxLength }), - { minLength, maxLength }, - ) -} - -Object_assign( - string_, - bindUserExtensions(string_, userExtensions), -) - -declare namespace string_ { - interface core extends string_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: string - tag: URI.string - get def(): this['_type'] - } - interface methods { - minLength?: number - maxLength?: number - min>(minLength: Min): string_.Min - max>(maxLength: Max): string_.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ maxLength: number }] - ? string_.between<[min: Min, max: Self['maxLength']]> - : string_.min - ; - type Max - = [Self] extends [{ minLength: number }] - ? string_.between<[min: Self['minLength'], max: Max]> - : string_.max - ; - interface min extends string_ { minLength: Min } - interface max extends string_ { maxLength: Max } - interface between extends string_ { - minLength: Bounds[0] - maxLength: Bounds[1] - } -} diff --git a/packages/schema-generator/src/__schemas__/symbol.ts b/packages/schema-generator/src/__schemas__/symbol.ts deleted file mode 100644 index 201b1ec0..00000000 --- a/packages/schema-generator/src/__schemas__/symbol.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * symbol_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: symbol, right: symbol): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function symbolToJsonSchema() { return void 0 } - return symbolToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'symbol' } -export function toString(): 'symbol' { return 'symbol' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(symbolSchema: symbol_): validate { - validateSymbol.tag = URI.symbol - function validateSymbol(u: unknown, path = Array.of()) { - return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] - } - return validateSymbol -} -/// validate /// -////////////////////// - -export { symbol_ as symbol } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface symbol_ extends symbol_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } -SymbolSchema.tag = URI.symbol -SymbolSchema.def = Symbol() - -const symbol_ = Object_assign( - SymbolSchema, - userDefinitions, -) as symbol_ - -Object_assign(symbol_, userExtensions) - -declare namespace symbol_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.symbol - _type: symbol - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/__schemas__/tuple.ts b/packages/schema-generator/src/__schemas__/tuple.ts deleted file mode 100644 index eb0603bc..00000000 --- a/packages/schema-generator/src/__schemas__/tuple.ts +++ /dev/null @@ -1,225 +0,0 @@ -/** - * tuple schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Equal, - Join, - Returns, - SchemaOptions as Options, - TypeError, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - Array_isArray, - bindUserExtensions, - getConfig, - has, - Object_assign, - Object_hasOwn, - Object_is, - parseArgs, - symbol, - tuple as tuple$, - URI -} from '@traversable/registry' -import type { - Entry, - FirstOptionalItem, - invalid, - Schema, - SchemaLike, - TupleType, - ValidateTuple -} from '../_namespace.js' -import type { optional } from './optional.js' -import type { t } from '../_exports.js' -import type { MinItems } from '@traversable/schema-to-json-schema' -import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' -import { hasToString } from '@traversable/schema-to-string' -import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' -import { Errors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal - -export function equals(tupleSchema: tuple): equals -export function equals(tupleSchema: tuple): equals -export function equals(tupleSchema: tuple) { - function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { - if (Object_is(l, r)) return true - if (Array_isArray(l)) { - if (!Array_isArray(r)) return false - for (let ix = tupleSchema.def.length; ix-- !== 0;) { - if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue - if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false - if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false - if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { - if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false - } - } - return true - } - return false - } - return tupleEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): { - type: 'array', - items: { [I in keyof T]: Returns } - additionalItems: false - minItems: MinItems - maxItems: T['length' & keyof T] - } -} - -export function toJsonSchema(tupleSchema: tuple): toJsonSchema -export function toJsonSchema({ def }: tuple): () => { - type: 'array' - items: unknown - additionalItems: false - minItems?: {} - maxItems?: number -} { - function tupleToJsonSchema() { - let min = minItems(def) - let max = def.length - let items = applyTupleOptionality(def, { min, max }) - return { - type: 'array' as const, - additionalItems: false as const, - items, - minItems: min, - maxItems: max, - } - } - return tupleToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - (): never | `[${Join<{ - [I in keyof T]: `${ - /* @ts-expect-error */ - T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType - }` - }, ', '>}]` -} - -export function toString(tupleSchema: tuple): toString -export function toString(tupleSchema: tuple): () => string { - let isOptional = has('tag', (tag) => tag === URI.optional) - function stringToString() { - return Array_isArray(tupleSchema.def) - ? `[${tupleSchema.def.map( - (x) => isOptional(x) - ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` - : hasToString(x) ? x.toString() : 'unknown' - ).join(', ')}]` : 'unknown[]' - } - return stringToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate -export function validate(tupleSchema: tuple<[...S]>): validate -export function validate(tupleSchema: tuple<[...S]>): validate -export function validate(tupleSchema: tuple<[...S]>): Validate { - validateTuple.tag = URI.tuple - let isOptional = has('tag', (tag) => tag === URI.optional) - function validateTuple(u: unknown, path = Array.of()) { - let errors = Array.of() - if (!Array_isArray(u)) return [Errors.array(u, path)] - for (let i = 0; i < tupleSchema.def.length; i++) { - if (!(i in u) && !(isOptional(tupleSchema.def[i].validate))) { - errors.push(Errors.missingIndex(u, [...path, i])) - continue - } - let results = tupleSchema.def[i].validate(u[i], [...path, i]) - if (results !== true) { - for (let j = 0; j < results.length; j++) errors.push(results[j]) - results.push(Errors.arrayElement(u[i], [...path, i])) - } - } - if (u.length > tupleSchema.def.length) { - for (let k = tupleSchema.def.length; k < u.length; k++) { - let excess = u[k] - errors.push(Errors.excessItems(excess, [...path, k])) - } - } - return errors.length === 0 || errors - } - return validateTuple -} -/// validate /// -////////////////////// - -export { tuple } - -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> -function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { - return tuple.def(...parseArgs(getConfig().schema, args)) -} - -interface tuple extends tuple.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -namespace tuple { - export let userDefinitions: Record = { - } as tuple - export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple - export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const opt = opt_ || xs.findIndex(has(symbol.optional)) - const options = { - ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) - } satisfies tuple.InternalOptions - const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) - function TupleSchema(src: unknown) { return predicate(src) } - TupleSchema.tag = URI.tuple - TupleSchema.def = xs - TupleSchema.opt = opt - Object_assign(TupleSchema, tuple.userDefinitions) - return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) - } -} - -declare namespace tuple { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.tuple - _type: TupleType - opt: FirstOptionalItem - def: S - } - type type> = never | T - type InternalOptions = { minLength?: number } - type validate = ValidateTuple> - - type from - = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T -} diff --git a/packages/schema-generator/src/__schemas__/undefined.ts b/packages/schema-generator/src/__schemas__/undefined.ts deleted file mode 100644 index dfbc9410..00000000 --- a/packages/schema-generator/src/__schemas__/undefined.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * undefined_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: undefined, right: undefined): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function undefinedToJsonSchema(): void { return void 0 } - return undefinedToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'undefined' } -export function toString(): 'undefined' { return 'undefined' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(undefinedSchema: undefined_): validate { - validateUndefined.tag = URI.undefined - function validateUndefined(u: unknown, path = Array.of()) { - return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] - } - return validateUndefined -} -/// validate /// -////////////////////// - -export { undefined_ as undefined } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface undefined_ extends undefined_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } -UndefinedSchema.tag = URI.undefined -UndefinedSchema.def = void 0 as undefined - -const undefined_ = Object_assign( - UndefinedSchema, - userDefinitions, -) as undefined_ - -Object_assign(undefined_, userExtensions) - -declare namespace undefined_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.undefined - _type: undefined - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/__schemas__/union.ts b/packages/schema-generator/src/__schemas__/union.ts deleted file mode 100644 index 0bf23565..00000000 --- a/packages/schema-generator/src/__schemas__/union.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * union schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Equal, - Join, - Returns, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - Array_isArray, - bindUserExtensions, - isUnknown as isAny, - Object_assign, - Object_is, - union as union$, - URI -} from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../_exports.js' -import { getSchema } from '@traversable/schema-to-json-schema' -import { callToString } from '@traversable/schema-to-string' -import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(unionSchema: union<[...S]>): equals -export function equals(unionSchema: union<[...S]>): equals -export function equals({ def }: union<{ equals: Equal }[]>): Equal { - function unionEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - for (let ix = def.length; ix-- !== 0;) - if (def[ix].equals(l, r)) return true - return false - } - return unionEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): { anyOf: { [I in keyof T]: Returns } } -} - -export function toJsonSchema(unionSchema: union): toJsonSchema -export function toJsonSchema(unionSchema: union): toJsonSchema -export function toJsonSchema({ def }: union): () => {} { - return function unionToJsonSchema() { - return { - anyOf: def.map(getSchema) - } - } -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - (): never | [T] extends [readonly []] ? 'never' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` -} - -export function toString(unionSchema: union): toString -export function toString({ def }: union): () => string { - function unionToString() { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' - } - return unionToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate - -export function validate(unionSchema: union): validate -export function validate(unionSchema: union): validate -export function validate({ def }: union) { - validateUnion.tag = URI.union - function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { - // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; - let errors = Array.of() - for (let i = 0; i < def.length; i++) { - let results = def[i].validate(u, path) - if (results === true) { - // validateUnion.optional = 0 - return true - } - for (let j = 0; j < results.length; j++) errors.push(results[j]) - } - // validateUnion.optional = 0 - return errors.length === 0 || errors - } - return validateUnion -} -/// validate /// -////////////////////// - -export function union(...schemas: S): union -export function union }>(...schemas: S): union -export function union(...schemas: unknown[]) { - return union.def(schemas) -} - -export interface union extends union.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export namespace union { - export let userDefinitions: Record = { - } as Partial> - export function def(xs: T): union - export function def(xs: unknown[]) { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const predicate = xs.every(_isPredicate) ? union$(xs) : isAny - function UnionSchema(src: unknown): src is unknown { return predicate(src) } - UnionSchema.tag = URI.union - UnionSchema.def = xs - Object_assign(UnionSchema, union.userDefinitions) - return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) - } -} - -export declare namespace union { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.union - _type: union.type - get def(): S - } - type type = never | T -} diff --git a/packages/schema-generator/src/__schemas__/unknown.ts b/packages/schema-generator/src/__schemas__/unknown.ts deleted file mode 100644 index caaf06c6..00000000 --- a/packages/schema-generator/src/__schemas__/unknown.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * unknown_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: any, right: any): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } -export function toJsonSchema(): toJsonSchema { - function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } - return anyToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'unknown' } -export function toString(): 'unknown' { return 'unknown' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(_?: unknown_): validate { - validateUnknown.tag = URI.unknown - function validateUnknown() { return true as const } - return validateUnknown -} -/// validate /// -////////////////////// - -export { unknown_ as unknown } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface unknown_ extends unknown_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function UnknownSchema(src: unknown): src is unknown { return true } -UnknownSchema.tag = URI.unknown -UnknownSchema.def = void 0 as unknown - -const unknown_ = Object_assign( - UnknownSchema, - userDefinitions, -) as unknown_ - -Object_assign(unknown_, userExtensions) - -declare namespace unknown_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.unknown - _type: unknown - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/__schemas__/void.ts b/packages/schema-generator/src/__schemas__/void.ts deleted file mode 100644 index 41bd95b3..00000000 --- a/packages/schema-generator/src/__schemas__/void.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * void_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: void, right: void): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function voidToJsonSchema(): void { - return void 0 - } - return voidToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'void' } -export function toString(): 'void' { return 'void' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(voidSchema: void_): validate { - validateVoid.tag = URI.void - function validateVoid(u: unknown, path = Array.of()) { - return voidSchema(u) || [NullaryErrors.void(u, path)] - } - return validateVoid -} -/// validate /// -////////////////////// - -export { void_ as void, void_ } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface void_ extends void_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function VoidSchema(src: unknown): src is void { return src === void 0 } -VoidSchema.tag = URI.void -VoidSchema.def = void 0 as void - -const void_ = Object_assign( - VoidSchema, - userDefinitions, -) as void_ - -Object_assign(void_, userExtensions) - -declare namespace void_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.void - _type: void - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/temp/any/core.ts b/packages/schema-generator/src/temp/any/core.ts deleted file mode 100644 index 877f92af..00000000 --- a/packages/schema-generator/src/temp/any/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { any_ as any } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface any_ extends any_.core { - //<%= Types %> -} - -function AnySchema(src: unknown): src is any { return true } -AnySchema.tag = URI.any -AnySchema.def = void 0 as any - -const any_ = Object_assign( - AnySchema, - userDefinitions, -) as any_ - -Object_assign(any_, userExtensions) - -declare namespace any_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.any - _type: any - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/temp/any/equals.ts b/packages/schema-generator/src/temp/any/equals.ts deleted file mode 100644 index 09d8da5f..00000000 --- a/packages/schema-generator/src/temp/any/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from "@traversable/registry" -import { Object_is } from "@traversable/registry" - -export type equals = Equal -export function equals(left: unknown, right: unknown): boolean { - return Object_is(left, right) -} diff --git a/packages/schema-generator/src/temp/any/extension.ts b/packages/schema-generator/src/temp/any/extension.ts deleted file mode 100644 index c4ecbb88..00000000 --- a/packages/schema-generator/src/temp/any/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { toJsonSchema } from './toJsonSchema.js' -import { validate } from './validate.js' -import { toString } from './toString.js' -import { equals } from './equals.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/any/toJsonSchema.ts b/packages/schema-generator/src/temp/any/toJsonSchema.ts deleted file mode 100644 index 25336fc6..00000000 --- a/packages/schema-generator/src/temp/any/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } -export function toJsonSchema(): toJsonSchema { - function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } - return unknownToJsonSchema -} diff --git a/packages/schema-generator/src/temp/any/toString.ts b/packages/schema-generator/src/temp/any/toString.ts deleted file mode 100644 index f70aa050..00000000 --- a/packages/schema-generator/src/temp/any/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'any' } -export function toString(): 'any' { return 'any' } diff --git a/packages/schema-generator/src/temp/any/validate.ts b/packages/schema-generator/src/temp/any/validate.ts deleted file mode 100644 index d286c4b5..00000000 --- a/packages/schema-generator/src/temp/any/validate.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(_?: t.any): validate { - validateAny.tag = URI.any - function validateAny() { return true as const } - return validateAny -} diff --git a/packages/schema-generator/src/temp/array/core.ts b/packages/schema-generator/src/temp/array/core.ts deleted file mode 100644 index 65c8c03c..00000000 --- a/packages/schema-generator/src/temp/array/core.ts +++ /dev/null @@ -1,128 +0,0 @@ -import type { - Bounds, - Integer, - Unknown, -} from '@traversable/registry' -import { - Array_isArray, - array as arrayOf, - bindUserExtensions, - carryover, - within, - _isPredicate, - has, - Math_max, - Math_min, - Number_isSafeInteger, - Object_assign, - URI, -} from '@traversable/registry' - -import type { Guarded, Schema, SchemaLike } from '../namespace.js' - -import type { of } from './of.js' - -/** @internal */ -function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { - return Object_assign(function BoundedArraySchema(u: unknown) { - return Array_isArray(u) && within(bounds)(u.length) - }, carry, array(schema)) -} - -export interface array extends array.core { - //<%= Types %> -} - -export function array(schema: S, readonly: 'readonly'): readonlyArray -export function array(schema: S): array -export function array(schema: S): array>> -export function array(schema: S): array { - return array.def(schema) -} - -export namespace array { - export let userDefinitions: Record = { - //<%= Definitions %> - } as array - export function def(x: S, prev?: array): array - export function def(x: S, prev?: unknown): array - export function def(x: S, prev?: array): array - /* v8 ignore next 1 */ - export function def(x: unknown, prev?: unknown): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray - function ArraySchema(src: unknown) { return predicate(src) } - ArraySchema.tag = URI.array - ArraySchema.def = x - ArraySchema.min = function arrayMin(minLength: Min) { - return Object_assign( - boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), - { minLength }, - ) - } - ArraySchema.max = function arrayMax(maxLength: Max) { - return Object_assign( - boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), - { maxLength }, - ) - } - ArraySchema.between = function arrayBetween( - min: Min, - max: Max, - minLength = Math_min(min, max), - maxLength = Math_max(min, max) - ) { - return Object_assign( - boundedArray(x, { gte: minLength, lte: maxLength }), - { minLength, maxLength }, - ) - } - if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength - if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength - Object_assign(ArraySchema, userDefinitions) - return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) - } -} - -export declare namespace array { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.array - get def(): S - _type: S['_type' & keyof S][] - minLength?: number - maxLength?: number - min>(minLength: Min): array.Min - max>(maxLength: Max): array.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> - } - type Min - = [Self] extends [{ maxLength: number }] - ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> - : array.min - ; - type Max - = [Self] extends [{ minLength: number }] - ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> - : array.max - ; - interface min extends array { minLength: Min } - interface max extends array { maxLength: Max } - interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } - type type = never | T -} - -export const readonlyArray: { - (schema: S): readonlyArray - (schema: S): readonlyArray> -} = array -export interface readonlyArray { - (u: unknown): u is this['_type'] - tag: URI.array - def: S - _type: ReadonlyArray -} diff --git a/packages/schema-generator/src/temp/array/equals.ts b/packages/schema-generator/src/temp/array/equals.ts deleted file mode 100644 index aa06a7bf..00000000 --- a/packages/schema-generator/src/temp/array/equals.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { has, Array_isArray, Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' - -export type equals = never | Equal - -export function equals(arraySchema: t.array): equals -export function equals(arraySchema: t.array): equals -export function equals({ def }: t.array<{ equals: Equal }>): Equal { - let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is - function arrayEquals(l: unknown[], r: unknown[]): boolean { - if (Object_is(l, r)) return true - if (Array_isArray(l)) { - if (!Array_isArray(r)) return false - let len = l.length - if (len !== r.length) return false - for (let ix = len; ix-- !== 0;) - if (!equals(l[ix], r[ix])) return false - return true - } else return false - } - return arrayEquals -} - diff --git a/packages/schema-generator/src/temp/array/extension.ts b/packages/schema-generator/src/temp/array/extension.ts deleted file mode 100644 index 0927d4dd..00000000 --- a/packages/schema-generator/src/temp/array/extension.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - toString: toString - equals: equals - toJsonSchema: toJsonSchema - validate: validate -} - -export let Definitions = {} - -export let Extensions = { - toJsonSchema, - validate, - toString, - equals, -} diff --git a/packages/schema-generator/src/temp/array/toJsonSchema.ts b/packages/schema-generator/src/temp/array/toJsonSchema.ts deleted file mode 100644 index cbdda659..00000000 --- a/packages/schema-generator/src/temp/array/toJsonSchema.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { t } from '@traversable/schema-core' -import type * as T from '@traversable/registry' -import type { SizeBounds } from '@traversable/schema-to-json-schema' -import { hasSchema } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): never | T.Force< - & { type: 'array', items: T.Returns } - & T.PickIfDefined - > -} - -export function toJsonSchema>(arraySchema: T): toJsonSchema -export function toJsonSchema(arraySchema: T): toJsonSchema -export function toJsonSchema( - { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, -): () => { - type: 'array' - items: unknown - minLength?: number - maxLength?: number -} { - function arrayToJsonSchema() { - let items = hasSchema(def) ? def.toJsonSchema() : def - let out = { - type: 'array' as const, - items, - minLength, - maxLength, - } - if (typeof minLength !== 'number') delete out.minLength - if (typeof maxLength !== 'number') delete out.maxLength - return out - } - return arrayToJsonSchema -} diff --git a/packages/schema-generator/src/temp/array/toString.ts b/packages/schema-generator/src/temp/array/toString.ts deleted file mode 100644 index 5114ef0b..00000000 --- a/packages/schema-generator/src/temp/array/toString.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { t } from '@traversable/schema-core' - -export interface toString { - /* @ts-expect-error */ - (): never | `(${ReturnType})[]` -} - -export function toString(arraySchema: t.array): toString -export function toString(arraySchema: t.array): toString -export function toString({ def }: { def: unknown }) { - function arrayToString() { - let body = ( - !!def - && typeof def === 'object' - && 'toString' in def - && typeof def.toString === 'function' - ) ? def.toString() - : '${string}' - return ('(' + body + ')[]') - } - return arrayToString -} diff --git a/packages/schema-generator/src/temp/array/validate.ts b/packages/schema-generator/src/temp/array/validate.ts deleted file mode 100644 index 4d8e7958..00000000 --- a/packages/schema-generator/src/temp/array/validate.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { URI } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { Errors, NullaryErrors } from '@traversable/derive-validators' - -export type validate = never | ValidationFn -export function validate(arraySchema: t.array): validate -export function validate(arraySchema: t.array): validate -export function validate( - { def: { validate = () => true }, minLength, maxLength }: t.array -) { - validateArray.tag = URI.array - function validateArray(u: unknown, path = Array.of()) { - if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] - let errors = Array.of() - if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) - if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) - for (let i = 0, len = u.length; i < len; i++) { - let y = u[i] - let results = validate(y, [...path, i]) - if (results === true) continue - else errors.push(...results) - } - return errors.length === 0 || errors - } - return validateArray -} diff --git a/packages/schema-generator/src/temp/bigint/core.ts b/packages/schema-generator/src/temp/bigint/core.ts deleted file mode 100644 index f7d55261..00000000 --- a/packages/schema-generator/src/temp/bigint/core.ts +++ /dev/null @@ -1,99 +0,0 @@ -import type { Bounds, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Object_assign, - URI, - withinBig as within, -} from '@traversable/registry' - -export { bigint_ as bigint } - -/** @internal */ -function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ -function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ -function boundedBigInt(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedBigIntSchema(u: unknown) { - return bigint_(u) && within(bounds)(u) - }, carry, bigint_) -} - -interface bigint_ extends bigint_.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function BigIntSchema(src: unknown) { return typeof src === 'bigint' } -BigIntSchema.tag = URI.bigint -BigIntSchema.def = 0n - -const bigint_ = Object_assign( - BigIntSchema, - userDefinitions, -) as bigint_ - -bigint_.min = function bigIntMin(minimum) { - return Object_assign( - boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -bigint_.max = function bigIntMax(maximum) { - return Object_assign( - boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -bigint_.between = function bigIntBetween( - min, - max, - minimum = (max < min ? max : min), - maximum = (max < min ? min : max), -) { - return Object_assign( - boundedBigInt({ gte: minimum, lte: maximum }), - { minimum, maximum } - ) -} - -Object_assign( - bigint_, - bindUserExtensions(bigint_, userExtensions), -) - -declare namespace bigint_ { - interface core extends bigint_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: bigint - tag: URI.bigint - get def(): this['_type'] - minimum?: bigint - maximum?: bigint - } - type Min - = [Self] extends [{ maximum: bigint }] - ? bigint_.between<[min: X, max: Self['maximum']]> - : bigint_.min - ; - type Max - = [Self] extends [{ minimum: bigint }] - ? bigint_.between<[min: Self['minimum'], max: X]> - : bigint_.max - ; - interface methods { - min(minimum: Min): bigint_.Min - max(maximum: Max): bigint_.Max - between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> - } - interface min extends bigint_ { minimum: Min } - interface max extends bigint_ { maximum: Max } - interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } -} - diff --git a/packages/schema-generator/src/temp/bigint/equals.ts b/packages/schema-generator/src/temp/bigint/equals.ts deleted file mode 100644 index 7c98bbf1..00000000 --- a/packages/schema-generator/src/temp/bigint/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: bigint, right: bigint): boolean { - return Object_is(left, right) -} diff --git a/packages/schema-generator/src/temp/bigint/extension.ts b/packages/schema-generator/src/temp/bigint/extension.ts deleted file mode 100644 index 7a2b8c12..00000000 --- a/packages/schema-generator/src/temp/bigint/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/bigint/toJsonSchema.ts b/packages/schema-generator/src/temp/bigint/toJsonSchema.ts deleted file mode 100644 index f6c7bc5b..00000000 --- a/packages/schema-generator/src/temp/bigint/toJsonSchema.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function bigintToJsonSchema(): void { - return void 0 - } - return bigintToJsonSchema -} diff --git a/packages/schema-generator/src/temp/bigint/toString.ts b/packages/schema-generator/src/temp/bigint/toString.ts deleted file mode 100644 index 793c903e..00000000 --- a/packages/schema-generator/src/temp/bigint/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'bigint' } -export function toString(): 'bigint' { return 'bigint' } diff --git a/packages/schema-generator/src/temp/bigint/validate.ts b/packages/schema-generator/src/temp/bigint/validate.ts deleted file mode 100644 index 7e3284da..00000000 --- a/packages/schema-generator/src/temp/bigint/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(bigIntSchema: S): validate { - validateBigInt.tag = URI.bigint - function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { - return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] - } - return validateBigInt -} diff --git a/packages/schema-generator/src/temp/boolean/core.ts b/packages/schema-generator/src/temp/boolean/core.ts deleted file mode 100644 index c89adf4b..00000000 --- a/packages/schema-generator/src/temp/boolean/core.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { boolean_ as boolean } - -interface boolean_ extends boolean_.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } - -BooleanSchema.tag = URI.boolean -BooleanSchema.def = false - -const boolean_ = Object_assign( - BooleanSchema, - userDefinitions, -) as boolean_ - -Object_assign(boolean_, userExtensions) - -declare namespace boolean_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.boolean - _type: boolean - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/temp/boolean/equals.ts b/packages/schema-generator/src/temp/boolean/equals.ts deleted file mode 100644 index 306bb12b..00000000 --- a/packages/schema-generator/src/temp/boolean/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: boolean, right: boolean): boolean { - return Object_is(left, right) -} diff --git a/packages/schema-generator/src/temp/boolean/extension.ts b/packages/schema-generator/src/temp/boolean/extension.ts deleted file mode 100644 index 7a2b8c12..00000000 --- a/packages/schema-generator/src/temp/boolean/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/boolean/toJsonSchema.ts b/packages/schema-generator/src/temp/boolean/toJsonSchema.ts deleted file mode 100644 index d1b86f70..00000000 --- a/packages/schema-generator/src/temp/boolean/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): { type: 'boolean' } } -export function toJsonSchema(): toJsonSchema { - function booleanToJsonSchema() { return { type: 'boolean' as const } } - return booleanToJsonSchema -} diff --git a/packages/schema-generator/src/temp/boolean/toString.ts b/packages/schema-generator/src/temp/boolean/toString.ts deleted file mode 100644 index 3c408e57..00000000 --- a/packages/schema-generator/src/temp/boolean/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'boolean' } -export function toString(): 'boolean' { return 'boolean' } diff --git a/packages/schema-generator/src/temp/boolean/validate.ts b/packages/schema-generator/src/temp/boolean/validate.ts deleted file mode 100644 index 76cee261..00000000 --- a/packages/schema-generator/src/temp/boolean/validate.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(booleanSchema: t.boolean): validate { - validateBoolean.tag = URI.boolean - function validateBoolean(u: unknown, path = Array.of()) { - return booleanSchema(true as const) || [NullaryErrors.null(u, path)] - } - return validateBoolean -} diff --git a/packages/schema-generator/src/temp/eq/core.ts b/packages/schema-generator/src/temp/eq/core.ts deleted file mode 100644 index d701a0f6..00000000 --- a/packages/schema-generator/src/temp/eq/core.ts +++ /dev/null @@ -1,41 +0,0 @@ -import type { Mut, Mutable, SchemaOptions as Options, Unknown } from '@traversable/registry' -import { applyOptions, bindUserExtensions, _isPredicate, Object_assign, URI } from '@traversable/registry' - -export function eq>(value: V, options?: Options): eq> -export function eq(value: V, options?: Options): eq -export function eq(value: V, options?: Options): eq { - return eq.def(value, options) -} - -export interface eq extends eq.core { - //<%= Types %> -} - -export namespace eq { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(value: T, options?: Options): eq - /* v8 ignore next 1 */ - export function def(x: T, $?: Options): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const options = applyOptions($) - const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) - function EqSchema(src: unknown) { return predicate(src) } - EqSchema.tag = URI.eq - EqSchema.def = x - Object_assign(EqSchema, eq.userDefinitions) - return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) - } -} - -export declare namespace eq { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.eq - _type: V - get def(): V - } -} diff --git a/packages/schema-generator/src/temp/eq/equals.ts b/packages/schema-generator/src/temp/eq/equals.ts deleted file mode 100644 index 774127f6..00000000 --- a/packages/schema-generator/src/temp/eq/equals.ts +++ /dev/null @@ -1,11 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { laxEquals } from '@traversable/registry' -import type { t } from '@traversable/schema-core' - -export type equals = never | Equal -export function equals(eqSchema: t.eq): equals -export function equals(): Equal { - return function eqEquals(left: any, right: any) { - return laxEquals(left, right) - } -} diff --git a/packages/schema-generator/src/temp/eq/extension.ts b/packages/schema-generator/src/temp/eq/extension.ts deleted file mode 100644 index ee871114..00000000 --- a/packages/schema-generator/src/temp/eq/extension.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = {} - -export let Extensions = { - toString, - equals, - toJsonSchema, - validate, -} diff --git a/packages/schema-generator/src/temp/eq/toJsonSchema.ts b/packages/schema-generator/src/temp/eq/toJsonSchema.ts deleted file mode 100644 index 8edfd03a..00000000 --- a/packages/schema-generator/src/temp/eq/toJsonSchema.ts +++ /dev/null @@ -1,8 +0,0 @@ -import type { t } from '@traversable/schema-core' - -export interface toJsonSchema { (): { const: T } } -export function toJsonSchema(eqSchema: t.eq): toJsonSchema -export function toJsonSchema({ def }: t.eq) { - function eqToJsonSchema() { return { const: def } } - return eqToJsonSchema -} diff --git a/packages/schema-generator/src/temp/eq/toString.ts b/packages/schema-generator/src/temp/eq/toString.ts deleted file mode 100644 index 3c224bea..00000000 --- a/packages/schema-generator/src/temp/eq/toString.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Key } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { stringify } from '@traversable/schema-to-string' - -export interface toString { - (): [Key] extends [never] - ? [T] extends [symbol] ? 'symbol' : 'symbol' - : [T] extends [string] ? `'${T}'` : Key -} - -export function toString(eqSchema: t.eq): toString -export function toString({ def }: t.eq): () => string { - function eqToString(): string { - return typeof def === 'symbol' ? 'symbol' : stringify(def) - } - return eqToString -} diff --git a/packages/schema-generator/src/temp/eq/validate.ts b/packages/schema-generator/src/temp/eq/validate.ts deleted file mode 100644 index 2f02ba7d..00000000 --- a/packages/schema-generator/src/temp/eq/validate.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Equal, getConfig, URI } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import type { Validate } from '@traversable/derive-validators' -import { Errors } from '@traversable/derive-validators' - -export type validate = Validate -export function validate(eqSchema: t.eq): validate -export function validate({ def }: t.eq): validate { - validateEq.tag = URI.eq - function validateEq(u: unknown, path = Array.of()) { - let options = getConfig().schema - let equals = options?.eq?.equalsFn || Equal.lax - if (equals(def, u)) return true - else return [Errors.eq(u, path, def)] - } - return validateEq -} diff --git a/packages/schema-generator/src/temp/integer/core.ts b/packages/schema-generator/src/temp/integer/core.ts deleted file mode 100644 index 2969e5a1..00000000 --- a/packages/schema-generator/src/temp/integer/core.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { Bounds, Integer, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_min, - Math_max, - Number_isSafeInteger, - Object_assign, - URI, - within, -} from '@traversable/registry' - - -export { integer } - -/** @internal */ -function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer -function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer -function boundedInteger(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedIntegerSchema(u: unknown) { - return integer(u) && within(bounds)(u) - }, carry, integer) -} - -interface integer extends integer.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } -IntegerSchema.tag = URI.integer -IntegerSchema.def = 0 - -const integer = Object_assign( - IntegerSchema, - userDefinitions, -) as integer - -integer.min = function integerMin(minimum) { - return Object_assign( - boundedInteger({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -integer.max = function integerMax(maximum) { - return Object_assign( - boundedInteger({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -integer.between = function integerBetween( - min, - max, - minimum = Math_min(min, max), - maximum = Math_max(min, max), -) { - return Object_assign( - boundedInteger({ gte: minimum, lte: maximum }), - { minimum, maximum }, - ) -} - -Object_assign( - integer, - bindUserExtensions(integer, userExtensions), -) - -declare namespace integer { - interface core extends integer.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: number - tag: URI.integer - get def(): this['_type'] - minimum?: number - maximum?: number - } - interface methods { - min>(minimum: Min): integer.Min - max>(maximum: Max): integer.Max - between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ maximum: number }] - ? integer.between<[min: X, max: Self['maximum']]> - : integer.min - type Max - = [Self] extends [{ minimum: number }] - ? integer.between<[min: Self['minimum'], max: X]> - : integer.max - interface min extends integer { minimum: Min } - interface max extends integer { maximum: Max } - interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } -} diff --git a/packages/schema-generator/src/temp/integer/equals.ts b/packages/schema-generator/src/temp/integer/equals.ts deleted file mode 100644 index 29fcd602..00000000 --- a/packages/schema-generator/src/temp/integer/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { SameValueNumber } from '@traversable/registry' - -export type equals = Equal -export function equals(left: number, right: number): boolean { - return SameValueNumber(left, right) -} diff --git a/packages/schema-generator/src/temp/integer/extension.ts b/packages/schema-generator/src/temp/integer/extension.ts deleted file mode 100644 index 1f68a7e8..00000000 --- a/packages/schema-generator/src/temp/integer/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toString, -} - -export let Extensions = { - toJsonSchema, - validate, -} diff --git a/packages/schema-generator/src/temp/integer/toJsonSchema.ts b/packages/schema-generator/src/temp/integer/toJsonSchema.ts deleted file mode 100644 index d531e292..00000000 --- a/packages/schema-generator/src/temp/integer/toJsonSchema.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Force, PickIfDefined } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import type { NumericBounds } from '@traversable/schema-to-json-schema' -import { getNumericBounds } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: t.integer): toJsonSchema { - function integerToJsonSchema() { - const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) - let bounds: NumericBounds = {} - if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum - if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum - if (typeof minimum === 'number') bounds.minimum = minimum - if (typeof maximum === 'number') bounds.maximum = maximum - return { - type: 'integer' as const, - ...bounds, - } - } - return integerToJsonSchema -} diff --git a/packages/schema-generator/src/temp/integer/toString.ts b/packages/schema-generator/src/temp/integer/toString.ts deleted file mode 100644 index 912565e6..00000000 --- a/packages/schema-generator/src/temp/integer/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'number' } -export function toString(): 'number' { return 'number' } diff --git a/packages/schema-generator/src/temp/integer/validate.ts b/packages/schema-generator/src/temp/integer/validate.ts deleted file mode 100644 index 5f044c5d..00000000 --- a/packages/schema-generator/src/temp/integer/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(integerSchema: S): validate { - validateInteger.tag = URI.integer - function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { - return integerSchema(u) || [NullaryErrors.integer(u, path)] - } - return validateInteger -} diff --git a/packages/schema-generator/src/temp/intersect/core.ts b/packages/schema-generator/src/temp/intersect/core.ts deleted file mode 100644 index a3f89c92..00000000 --- a/packages/schema-generator/src/temp/intersect/core.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - _isPredicate, - bindUserExtensions, - intersect as intersect$, - isUnknown as isAny, - Object_assign, - URI, -} from '@traversable/registry' - -import type { Entry, IntersectType, Schema, SchemaLike } from '../namespace.js' - -export function intersect(...schemas: S): intersect -export function intersect }>(...schemas: S): intersect -export function intersect(...schemas: readonly unknown[]) { - return intersect.def(schemas) -} - -export interface intersect extends intersect.core { - //<%= Types %> -} - -export namespace intersect { - export let userDefinitions: Record = { - //<%= Definitions %> - } as intersect - export function def(xs: readonly [...T]): intersect - /* v8 ignore next 1 */ - export function def(xs: readonly unknown[]): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny - function IntersectSchema(src: unknown) { return predicate(src) } - IntersectSchema.tag = URI.intersect - IntersectSchema.def = xs - Object_assign(IntersectSchema, intersect.userDefinitions) - return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) - } -} - -export declare namespace intersect { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.intersect - get def(): S - _type: IntersectType - } - type type> = never | T -} diff --git a/packages/schema-generator/src/temp/intersect/equals.ts b/packages/schema-generator/src/temp/intersect/equals.ts deleted file mode 100644 index ce880aa1..00000000 --- a/packages/schema-generator/src/temp/intersect/equals.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' - -export type equals = Equal -export function equals(intersectSchema: t.intersect<[...S]>): equals -export function equals(intersectSchema: t.intersect<[...S]>): equals -export function equals({ def }: t.intersect<{ equals: Equal }[]>): Equal { - function intersectEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - for (let ix = def.length; ix-- !== 0;) - if (!def[ix].equals(l, r)) return false - return true - } - return intersectEquals -} diff --git a/packages/schema-generator/src/temp/intersect/extension.ts b/packages/schema-generator/src/temp/intersect/extension.ts deleted file mode 100644 index 0927d4dd..00000000 --- a/packages/schema-generator/src/temp/intersect/extension.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - toString: toString - equals: equals - toJsonSchema: toJsonSchema - validate: validate -} - -export let Definitions = {} - -export let Extensions = { - toJsonSchema, - validate, - toString, - equals, -} diff --git a/packages/schema-generator/src/temp/intersect/toJsonSchema.ts b/packages/schema-generator/src/temp/intersect/toJsonSchema.ts deleted file mode 100644 index d3942a94..00000000 --- a/packages/schema-generator/src/temp/intersect/toJsonSchema.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Returns } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { getSchema } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): { - allOf: { [I in keyof T]: Returns } - } -} - -export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema -export function toJsonSchema(intersectSchema: t.intersect): toJsonSchema -export function toJsonSchema({ def }: t.intersect): () => {} { - function intersectToJsonSchema() { - return { - allOf: def.map(getSchema) - } - } - return intersectToJsonSchema -} diff --git a/packages/schema-generator/src/temp/intersect/toString.ts b/packages/schema-generator/src/temp/intersect/toString.ts deleted file mode 100644 index 2e179159..00000000 --- a/packages/schema-generator/src/temp/intersect/toString.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Join } from '@traversable/registry' -import { Array_isArray } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { callToString } from '@traversable/schema-to-string' - -export interface toString { - (): never | [T] extends [readonly []] ? 'unknown' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` -} - -export function toString(intersectSchema: t.intersect): toString -export function toString({ def }: t.intersect): () => string { - function intersectToString() { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' - } - return intersectToString -} diff --git a/packages/schema-generator/src/temp/intersect/validate.ts b/packages/schema-generator/src/temp/intersect/validate.ts deleted file mode 100644 index 8a586df2..00000000 --- a/packages/schema-generator/src/temp/intersect/validate.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { URI } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' - -export type validate = Validate - -export function validate(intersectSchema: t.intersect): validate -export function validate(intersectSchema: t.intersect): validate -export function validate({ def }: t.intersect) { - validateIntersect.tag = URI.intersect - function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { - let errors = Array.of() - for (let i = 0; i < def.length; i++) { - let results = def[i].validate(u, path) - if (results !== true) - for (let j = 0; j < results.length; j++) errors.push(results[j]) - } - return errors.length === 0 || errors - } - return validateIntersect -} diff --git a/packages/schema-generator/src/temp/never/core.ts b/packages/schema-generator/src/temp/never/core.ts deleted file mode 100644 index a0077281..00000000 --- a/packages/schema-generator/src/temp/never/core.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { never_ as never } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface never_ extends never_.core { - //<%= Types %> -} - -function NeverSchema(src: unknown): src is never { return false } -NeverSchema.tag = URI.never; -NeverSchema.def = void 0 as never - -const never_ = Object_assign( - NeverSchema, - userDefinitions, -) as never_ - -Object_assign(never_, userExtensions) - -export declare namespace never_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.never - _type: never - get def(): this['_type'] - } -} - diff --git a/packages/schema-generator/src/temp/never/equals.ts b/packages/schema-generator/src/temp/never/equals.ts deleted file mode 100644 index 3ed89421..00000000 --- a/packages/schema-generator/src/temp/never/equals.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Equal } from '@traversable/registry' - -export type equals = Equal -export function equals(left: never, right: never): boolean { - return false -} diff --git a/packages/schema-generator/src/temp/never/extension.ts b/packages/schema-generator/src/temp/never/extension.ts deleted file mode 100644 index 7a2b8c12..00000000 --- a/packages/schema-generator/src/temp/never/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/never/toJsonSchema.ts b/packages/schema-generator/src/temp/never/toJsonSchema.ts deleted file mode 100644 index d22338df..00000000 --- a/packages/schema-generator/src/temp/never/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): never } -export function toJsonSchema(): toJsonSchema { - function neverToJsonSchema() { return void 0 as never } - return neverToJsonSchema -} diff --git a/packages/schema-generator/src/temp/never/toString.ts b/packages/schema-generator/src/temp/never/toString.ts deleted file mode 100644 index aaabf80d..00000000 --- a/packages/schema-generator/src/temp/never/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'never' } -export function toString(): 'never' { return 'never' } diff --git a/packages/schema-generator/src/temp/never/validate.ts b/packages/schema-generator/src/temp/never/validate.ts deleted file mode 100644 index 52ce9ee5..00000000 --- a/packages/schema-generator/src/temp/never/validate.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(_?: t.never): validate { - validateNever.tag = URI.never - function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } - return validateNever -} diff --git a/packages/schema-generator/src/temp/null/core.ts b/packages/schema-generator/src/temp/null/core.ts deleted file mode 100644 index befebeb5..00000000 --- a/packages/schema-generator/src/temp/null/core.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { null_ as null, null_ } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface null_ extends null_.core { - //<%= Types %> -} - -function NullSchema(src: unknown): src is null { return src === null } -NullSchema.def = null -NullSchema.tag = URI.null - -const null_ = Object_assign( - NullSchema, - userDefinitions, -) as null_ - -Object_assign( - null_, - userExtensions, -) - -declare namespace null_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.null - _type: null - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/temp/null/equals.ts b/packages/schema-generator/src/temp/null/equals.ts deleted file mode 100644 index 12c2f636..00000000 --- a/packages/schema-generator/src/temp/null/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: null, right: null): boolean { - return Object_is(left, right) -} diff --git a/packages/schema-generator/src/temp/null/extension.ts b/packages/schema-generator/src/temp/null/extension.ts deleted file mode 100644 index 7a2b8c12..00000000 --- a/packages/schema-generator/src/temp/null/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/null/toJsonSchema.ts b/packages/schema-generator/src/temp/null/toJsonSchema.ts deleted file mode 100644 index 7a3b7c3a..00000000 --- a/packages/schema-generator/src/temp/null/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): { type: 'null', enum: [null] } } -export function toJsonSchema(): toJsonSchema { - function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } - return nullToJsonSchema -} diff --git a/packages/schema-generator/src/temp/null/toString.ts b/packages/schema-generator/src/temp/null/toString.ts deleted file mode 100644 index 35c3aef8..00000000 --- a/packages/schema-generator/src/temp/null/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'null' } -export function toString(): 'null' { return 'null' } diff --git a/packages/schema-generator/src/temp/null/validate.ts b/packages/schema-generator/src/temp/null/validate.ts deleted file mode 100644 index b2abe36b..00000000 --- a/packages/schema-generator/src/temp/null/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(nullSchema: t.null): validate { - validateNull.tag = URI.null - function validateNull(u: unknown, path = Array.of()) { - return nullSchema(u) || [NullaryErrors.null(u, path)] - } - return validateNull -} diff --git a/packages/schema-generator/src/temp/number/core.ts b/packages/schema-generator/src/temp/number/core.ts deleted file mode 100644 index 5972ae85..00000000 --- a/packages/schema-generator/src/temp/number/core.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { Bounds, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_min, - Math_max, - Object_assign, - URI, - within, -} from '@traversable/registry' - - -export { number_ as number } - -interface number_ extends number_.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function NumberSchema(src: unknown) { return typeof src === 'number' } -NumberSchema.tag = URI.number -NumberSchema.def = 0 - -const number_ = Object_assign( - NumberSchema, - userDefinitions, -) as number_ - -number_.min = function numberMin(minimum) { - return Object_assign( - boundedNumber({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -number_.max = function numberMax(maximum) { - return Object_assign( - boundedNumber({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -number_.moreThan = function numberMoreThan(exclusiveMinimum) { - return Object_assign( - boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), - { exclusiveMinimum }, - ) -} -number_.lessThan = function numberLessThan(exclusiveMaximum) { - return Object_assign( - boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), - { exclusiveMaximum }, - ) -} -number_.between = function numberBetween( - min, - max, - minimum = Math_min(min, max), - maximum = Math_max(min, max), -) { - return Object_assign( - boundedNumber({ gte: minimum, lte: maximum }), - { minimum, maximum }, - ) -} - -Object_assign( - number_, - bindUserExtensions(number_, userExtensions), -) - -function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ -function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ -function boundedNumber(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedNumberSchema(u: unknown) { - return typeof u === 'number' && within(bounds)(u) - }, carry, number_) -} - -declare namespace number_ { - interface core extends number_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: number - tag: URI.number - get def(): this['_type'] - minimum?: number - maximum?: number - exclusiveMinimum?: number - exclusiveMaximum?: number - } - interface methods { - min(minimum: Min): number_.Min - max(maximum: Max): number_.Max - moreThan(moreThan: Min): ExclusiveMin - lessThan(lessThan: Max): ExclusiveMax - between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ exclusiveMaximum: number }] - ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> - : [Self] extends [{ maximum: number }] - ? number_.between<[min: X, max: Self['maximum']]> - : number_.min - ; - type Max - = [Self] extends [{ exclusiveMinimum: number }] - ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> - : [Self] extends [{ minimum: number }] - ? number_.between<[min: Self['minimum'], max: X]> - : number_.max - ; - type ExclusiveMin - = [Self] extends [{ exclusiveMaximum: number }] - ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> - : [Self] extends [{ maximum: number }] - ? number_.maxStrictMin<[min: X, Self['maximum']]> - : number_.moreThan - ; - type ExclusiveMax - = [Self] extends [{ exclusiveMinimum: number }] - ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> - : [Self] extends [{ minimum: number }] - ? number_.minStrictMax<[Self['minimum'], min: X]> - : number_.lessThan - ; - interface min extends number_ { minimum: Min } - interface max extends number_ { maximum: Max } - interface moreThan extends number_ { exclusiveMinimum: Min } - interface lessThan extends number_ { exclusiveMaximum: Max } - interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } - interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } - interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } - interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } -} diff --git a/packages/schema-generator/src/temp/number/equals.ts b/packages/schema-generator/src/temp/number/equals.ts deleted file mode 100644 index 29fcd602..00000000 --- a/packages/schema-generator/src/temp/number/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { SameValueNumber } from '@traversable/registry' - -export type equals = Equal -export function equals(left: number, right: number): boolean { - return SameValueNumber(left, right) -} diff --git a/packages/schema-generator/src/temp/number/extension.ts b/packages/schema-generator/src/temp/number/extension.ts deleted file mode 100644 index 1a06ace0..00000000 --- a/packages/schema-generator/src/temp/number/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - toString: toString - equals: equals - toJsonSchema: toJsonSchema - validate: validate -} - -export let Definitions = { - toString, - equals, -} - -export let Extensions = { - toJsonSchema, - validate, -} diff --git a/packages/schema-generator/src/temp/number/toJsonSchema.ts b/packages/schema-generator/src/temp/number/toJsonSchema.ts deleted file mode 100644 index 7146d478..00000000 --- a/packages/schema-generator/src/temp/number/toJsonSchema.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Force, PickIfDefined } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import type { NumericBounds } from '@traversable/schema-to-json-schema' -import { getNumericBounds } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: t.number): toJsonSchema { - function numberToJsonSchema() { - const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) - let bounds: NumericBounds = {} - if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum - if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum - if (typeof minimum === 'number') bounds.minimum = minimum - if (typeof maximum === 'number') bounds.maximum = maximum - return { - type: 'number' as const, - ...bounds, - } - } - return numberToJsonSchema -} diff --git a/packages/schema-generator/src/temp/number/toString.ts b/packages/schema-generator/src/temp/number/toString.ts deleted file mode 100644 index 912565e6..00000000 --- a/packages/schema-generator/src/temp/number/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'number' } -export function toString(): 'number' { return 'number' } diff --git a/packages/schema-generator/src/temp/number/validate.ts b/packages/schema-generator/src/temp/number/validate.ts deleted file mode 100644 index 416f639b..00000000 --- a/packages/schema-generator/src/temp/number/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(numberSchema: S): validate { - validateNumber.tag = URI.number - function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { - return numberSchema(u) || [NullaryErrors.number(u, path)] - } - return validateNumber -} diff --git a/packages/schema-generator/src/temp/object/core.ts b/packages/schema-generator/src/temp/object/core.ts deleted file mode 100644 index ad856e41..00000000 --- a/packages/schema-generator/src/temp/object/core.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { Force, SchemaOptions as Options, Unknown } from '@traversable/registry' -import { - applyOptions, - Array_isArray, - bindUserExtensions, - has, - _isPredicate, - Object_assign, - Object_keys, - record as record$, - object as object$, - isAnyObject, - symbol, - URI, -} from '@traversable/registry' - -import type { Entry, Optional, Required, Schema, SchemaLike } from '../namespace.js' - -export { object_ as object } - -function object_< - S extends { [x: string]: Schema }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_< - S extends { [x: string]: SchemaLike }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_(schemas: S, options?: Options) { - return object_.def(schemas, options) -} - -interface object_ extends object_.core { - //<%= Types %> -} - -namespace object_ { - export let userDefinitions: Record = { - //<%= Definitions %> - } as object_ - export function def(xs: T, $?: Options, opt?: string[]): object_ - /* v8 ignore next 1 */ - export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const keys = Object_keys(xs) - const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) - const req = keys.filter((k) => !has(symbol.optional)(xs[k])) - const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) - function ObjectSchema(src: unknown) { return predicate(src) } - ObjectSchema.tag = URI.object - ObjectSchema.def = xs - ObjectSchema.opt = opt - ObjectSchema.req = req - Object_assign(ObjectSchema, userDefinitions) - return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) - } -} - -declare namespace object_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: object_.type - tag: URI.object - get def(): S - opt: Optional // TODO: use object_.Opt? - req: Required // TODO: use object_.Req? - } - type Opt = symbol.optional extends keyof S[K] ? never : K - type Req = symbol.optional extends keyof S[K] ? K : never - type type = Force< - & { [K in keyof S as Opt]-?: S[K]['_type' & keyof S[K]] } - & { [K in keyof S as Req]+?: S[K]['_type' & keyof S[K]] } - > -} diff --git a/packages/schema-generator/src/temp/object/equals.ts b/packages/schema-generator/src/temp/object/equals.ts deleted file mode 100644 index ecda0b32..00000000 --- a/packages/schema-generator/src/temp/object/equals.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type * as T from '@traversable/registry' -import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' - -export type equals = never | T.Equal -export function equals(objectSchema: t.object): equals> -export function equals(objectSchema: t.object): equals> -export function equals({ def }: t.object): equals> { - function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { - if (Object_is(l, r)) return true - if (!l || typeof l !== 'object' || Array_isArray(l)) return false - if (!r || typeof r !== 'object' || Array_isArray(r)) return false - for (const k in def) { - const lHas = Object_hasOwn(l, k) - const rHas = Object_hasOwn(r, k) - if (lHas) { - if (!rHas) return false - if (!def[k].equals(l[k], r[k])) return false - } - if (rHas) { - if (!lHas) return false - if (!def[k].equals(l[k], r[k])) return false - } - if (!def[k].equals(l[k], r[k])) return false - } - return true - } - return objectEquals -} - -// export type equals = never | T.Equal -// export function equals(objectSchema: t.object): equals> -// export function equals(objectSchema: t.object): equals> -// export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { -// function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { -// if (Object_is(l, r)) return true -// if (!l || typeof l !== 'object' || Array_isArray(l)) return false -// if (!r || typeof r !== 'object' || Array_isArray(r)) return false -// for (const k in def) { -// const lHas = Object_hasOwn(l, k) -// const rHas = Object_hasOwn(r, k) -// if (lHas) { -// if (!rHas) return false -// if (!def[k].equals(l[k], r[k])) return false -// } -// if (rHas) { -// if (!lHas) return false -// if (!def[k].equals(l[k], r[k])) return false -// } -// if (!def[k].equals(l[k], r[k])) return false -// } -// return true -// } -// return objectEquals -// } diff --git a/packages/schema-generator/src/temp/object/extension.ts b/packages/schema-generator/src/temp/object/extension.ts deleted file mode 100644 index ee871114..00000000 --- a/packages/schema-generator/src/temp/object/extension.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = {} - -export let Extensions = { - toString, - equals, - toJsonSchema, - validate, -} diff --git a/packages/schema-generator/src/temp/object/toJsonSchema.ts b/packages/schema-generator/src/temp/object/toJsonSchema.ts deleted file mode 100644 index 05dc8f9e..00000000 --- a/packages/schema-generator/src/temp/object/toJsonSchema.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Returns } from '@traversable/registry' -import { fn, Object_keys } from '@traversable/registry' -import type { RequiredKeys } from '@traversable/schema-to-json-schema' -import { isRequired, property } from '@traversable/schema-to-json-schema' -import type { t } from '@traversable/schema-core' - -export interface toJsonSchema = RequiredKeys> { - (): { - type: 'object' - required: { [I in keyof KS]: KS[I] & string } - properties: { [K in keyof T]: Returns } - } -} - -export function toJsonSchema(objectSchema: t.object): toJsonSchema -export function toJsonSchema(objectSchema: t.object): toJsonSchema -export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { - const required = Object_keys(def).filter(isRequired(def)) - function objectToJsonSchema() { - return { - type: 'object' as const, - required, - properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), - } - } - return objectToJsonSchema -} diff --git a/packages/schema-generator/src/temp/object/toString.ts b/packages/schema-generator/src/temp/object/toString.ts deleted file mode 100644 index 3edc0bb8..00000000 --- a/packages/schema-generator/src/temp/object/toString.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { Join, UnionToTuple } from '@traversable/registry' -import { symbol } from '@traversable/registry' -import type { t } from '@traversable/schema-core' - -/** @internal */ -type Symbol_optional = typeof Symbol_optional -const Symbol_optional: typeof symbol.optional = symbol.optional - -/** @internal */ -const hasOptionalSymbol = (u: unknown): u is { toString(): T } => - !!u && typeof u === 'function' - && Symbol_optional in u - && typeof u[Symbol_optional] === 'number' - -/** @internal */ -const hasToString = (x: unknown): x is { toString(): string } => - !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' - -export interface toString> { - (): never - | [keyof T] extends [never] ? '{}' - /* @ts-expect-error */ - : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` -} - - -export function toString>(objectSchema: t.object): toString -export function toString({ def }: t.object) { - function objectToString() { - if (!!def && typeof def === 'object') { - const entries = Object.entries(def) - if (entries.length === 0) return '{}' - else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" - }: ${hasToString(x) ? x.toString() : 'unknown' - }`).join(', ') - } }` - } - else return '{ [x: string]: unknown }' - } - - return objectToString -} \ No newline at end of file diff --git a/packages/schema-generator/src/temp/object/validate.ts b/packages/schema-generator/src/temp/object/validate.ts deleted file mode 100644 index eb06d116..00000000 --- a/packages/schema-generator/src/temp/object/validate.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { - Array_isArray, - has, - Object_keys, - Object_hasOwn, - typeName, - URI, -} from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { getConfig } from '@traversable/schema-core' -import type { ValidationError, Validator, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors, Errors, UnaryErrors } from '@traversable/derive-validators' - -/** @internal */ -let isObject = (u: unknown): u is { [x: string]: unknown } => - !!u && typeof u === 'object' && !Array_isArray(u) - -/** @internal */ -let isKeyOf = (k: keyof any, u: T): k is keyof T => - !!u && (typeof u === 'function' || typeof u === 'object') && k in u - -/** @internal */ -let isOptional = has('tag', (tag) => tag === URI.optional) - - -export type validate = never | ValidationFn - -export function validate(objectSchema: t.object): validate -export function validate(objectSchema: t.object): validate -export function validate(objectSchema: t.object): validate<{ [x: string]: unknown }> { - validateObject.tag = URI.object - function validateObject(u: unknown, path_ = Array.of()) { - // if (objectSchema(u)) return true - if (!isObject(u)) return [Errors.object(u, path_)] - let errors = Array.of() - let { schema: { optionalTreatment } } = getConfig() - let keys = Object_keys(objectSchema.def) - if (optionalTreatment === 'exactOptional') { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path = [...path_, k] - if (Object_hasOwn(u, k) && u[k] === undefined) { - if (isOptional(objectSchema.def[k].validate)) { - let tag = typeName(objectSchema.def[k].validate) - if (isKeyOf(tag, NullaryErrors)) { - let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] - errors.push(NullaryErrors[tag](...args)) - } - else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) - } - } - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - let tag = typeName(objectSchema.def[k].validate) - if (isKeyOf(tag, NullaryErrors)) { - errors.push(NullaryErrors[tag](u[k], path, tag)) - } - else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag].invalid(u[k], path)) - } - errors.push(...results) - } - else if (Object_hasOwn(u, k)) { - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - errors.push(...results) - continue - } else { - errors.push(UnaryErrors.object.missing(u, path)) - continue - } - } - } - else { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path = [...path_, k] - if (!Object_hasOwn(u, k)) { - if (!isOptional(objectSchema.def[k].validate)) { - errors.push(UnaryErrors.object.missing(u, path)) - continue - } - else { - if (!Object_hasOwn(u, k)) continue - if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { - if (u[k] === undefined) continue - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - for (let j = 0; j < results.length; j++) { - let result = results[j] - errors.push(result) - continue - } - } - } - } - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - for (let l = 0; l < results.length; l++) { - let result = results[l] - errors.push(result) - } - } - } - return errors.length === 0 || errors - } - - return validateObject -} diff --git a/packages/schema-generator/src/temp/of/core.ts b/packages/schema-generator/src/temp/of/core.ts deleted file mode 100644 index c02a9210..00000000 --- a/packages/schema-generator/src/temp/of/core.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -import type { - Entry, - Guard, - Guarded, - SchemaLike, -} from '../namespace.js' - -export interface of extends of.core { - //<%= Types %> -} - -export function of(typeguard: S): Entry -export function of(typeguard: S): of -export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { - typeguard.def = typeguard - return Object_assign(typeguard, of.prototype) -} - -export namespace of { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export let userExtensions: Record = { - //<%= Extensions %> - } - export function def(guard: T): of - /* v8 ignore next 6 */ - export function def(guard: T) { - function InlineSchema(src: unknown) { return guard(src) } - InlineSchema.tag = URI.inline - InlineSchema.def = guard - return InlineSchema - } -} - -export declare namespace of { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: Guarded - tag: URI.inline - get def(): S - } - type type> = never | T -} diff --git a/packages/schema-generator/src/temp/of/equals.ts b/packages/schema-generator/src/temp/of/equals.ts deleted file mode 100644 index 38809729..00000000 --- a/packages/schema-generator/src/temp/of/equals.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Equal } from '@traversable/registry' - -export type equals = Equal -export function equals(left: T, right: T): boolean { - return Equal.lax(left, right) -} diff --git a/packages/schema-generator/src/temp/of/extension.ts b/packages/schema-generator/src/temp/of/extension.ts deleted file mode 100644 index 7a2b8c12..00000000 --- a/packages/schema-generator/src/temp/of/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/of/toJsonSchema.ts b/packages/schema-generator/src/temp/of/toJsonSchema.ts deleted file mode 100644 index c8aaf62b..00000000 --- a/packages/schema-generator/src/temp/of/toJsonSchema.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function inlineToJsonSchema(): void { - return void 0 - } - return inlineToJsonSchema -} diff --git a/packages/schema-generator/src/temp/of/toString.ts b/packages/schema-generator/src/temp/of/toString.ts deleted file mode 100644 index 417e1048..00000000 --- a/packages/schema-generator/src/temp/of/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'unknown' } -export function toString(): 'unknown' { return 'unknown' } diff --git a/packages/schema-generator/src/temp/of/validate.ts b/packages/schema-generator/src/temp/of/validate.ts deleted file mode 100644 index c6557c41..00000000 --- a/packages/schema-generator/src/temp/of/validate.ts +++ /dev/null @@ -1,14 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(inlineSchema: t.of): validate { - validateInline.tag = URI.inline - function validateInline(u: unknown, path = Array.of()) { - return inlineSchema(u) || [NullaryErrors.inline(u, path)] - } - return validateInline -} - diff --git a/packages/schema-generator/src/temp/optional/core.ts b/packages/schema-generator/src/temp/optional/core.ts deleted file mode 100644 index 0574d117..00000000 --- a/packages/schema-generator/src/temp/optional/core.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - bindUserExtensions, - has, - _isPredicate, - optional as optional$, - Object_assign, - symbol, - URI, - isUnknown as isAny, -} from '@traversable/registry' - -import type { Entry, Schema, SchemaLike } from '../namespace.js' - -export function optional(schema: S): optional -export function optional(schema: S): optional> -export function optional(schema: S): optional { return optional.def(schema) } - -export interface optional extends optional.core { - //<%= Types %> -} - -export namespace optional { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(x: T): optional - export function def(x: T) { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = _isPredicate(x) ? optional$(x) : isAny - function OptionalSchema(src: unknown) { return predicate(src) } - OptionalSchema.tag = URI.optional - OptionalSchema.def = x - OptionalSchema[symbol.optional] = 1 - Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) - return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) - } - export const is - : (u: unknown) => u is optional - /* v8 ignore next 1 */ - = has('tag', (u) => u === URI.optional) -} - -export declare namespace optional { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.optional - _type: undefined | S['_type' & keyof S] - def: S - [symbol.optional]: number - } - export type type = never | T -} diff --git a/packages/schema-generator/src/temp/optional/equals.ts b/packages/schema-generator/src/temp/optional/equals.ts deleted file mode 100644 index 25944376..00000000 --- a/packages/schema-generator/src/temp/optional/equals.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' - -export type equals = never | Equal -export function equals(optionalSchema: t.optional): equals -export function equals(optionalSchema: t.optional): equals -export function equals({ def }: t.optional<{ equals: Equal }>): Equal { - return function optionalEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - return def.equals(l, r) - } -} diff --git a/packages/schema-generator/src/temp/optional/extension.ts b/packages/schema-generator/src/temp/optional/extension.ts deleted file mode 100644 index 0e7c4478..00000000 --- a/packages/schema-generator/src/temp/optional/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = {} - -export let Extensions = { - toString, - equals, - toJsonSchema, - validate, -} - diff --git a/packages/schema-generator/src/temp/optional/toJsonSchema.ts b/packages/schema-generator/src/temp/optional/toJsonSchema.ts deleted file mode 100644 index 82bff553..00000000 --- a/packages/schema-generator/src/temp/optional/toJsonSchema.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type { Force } from '@traversable/registry' -import type { Returns } from '@traversable/registry' -import { symbol } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' - -type Nullable = Force - -export interface toJsonSchema { - (): Nullable> - [symbol.optional]: number -} - -export function toJsonSchema(optionalSchema: t.optional): toJsonSchema -export function toJsonSchema({ def }: t.optional) { - function optionalToJsonSchema() { return getSchema(def) } - optionalToJsonSchema[symbol.optional] = wrapOptional(def) - return optionalToJsonSchema -} diff --git a/packages/schema-generator/src/temp/optional/toString.ts b/packages/schema-generator/src/temp/optional/toString.ts deleted file mode 100644 index f4c96cc8..00000000 --- a/packages/schema-generator/src/temp/optional/toString.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { callToString } from '@traversable/schema-to-string' - -export interface toString { - /* @ts-expect-error */ - (): never | `(${ReturnType} | undefined)` -} - -export function toString(optionalSchema: t.optional): toString -export function toString({ def }: t.optional): () => string { - function optionalToString(): string { - return '(' + callToString(def) + ' | undefined)' - } - return optionalToString -} diff --git a/packages/schema-generator/src/temp/optional/validate.ts b/packages/schema-generator/src/temp/optional/validate.ts deleted file mode 100644 index 70cd1e9d..00000000 --- a/packages/schema-generator/src/temp/optional/validate.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { URI } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import type { Validate, Validator, ValidationFn } from '@traversable/derive-validators' - -export type validate = Validate - -export function validate(optionalSchema: t.optional): validate -export function validate(optionalSchema: t.optional): validate -export function validate({ def }: t.optional): ValidationFn { - validateOptional.tag = URI.optional - validateOptional.optional = 1 - function validateOptional(u: unknown, path = Array.of()) { - if (u === void 0) return true - return def.validate(u, path) - } - return validateOptional -} diff --git a/packages/schema-generator/src/temp/record/core.ts b/packages/schema-generator/src/temp/record/core.ts deleted file mode 100644 index c4e54a9a..00000000 --- a/packages/schema-generator/src/temp/record/core.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - isAnyObject, - record as record$, - bindUserExtensions, - _isPredicate, - Object_assign, - URI, -} from '@traversable/registry' - -import type { Entry, Schema, SchemaLike } from '../namespace.js' - -export function record(schema: S): record -export function record(schema: S): record> -export function record(schema: Schema) { - return record.def(schema) -} - -export interface record extends record.core { - //<%= Types %> -} - -export namespace record { - export let userDefinitions: Record = { - //<%= Definitions %> - } - export function def(x: T): record - /* v8 ignore next 1 */ - export function def(x: unknown): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = _isPredicate(x) ? record$(x) : isAnyObject - function RecordSchema(src: unknown) { return predicate(src) } - RecordSchema.tag = URI.record - RecordSchema.def = x - Object_assign(RecordSchema, record.userDefinitions) - return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) - } -} - -export declare namespace record { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.record - get def(): S - _type: Record - } - export type type> = never | T -} diff --git a/packages/schema-generator/src/temp/record/equals.ts b/packages/schema-generator/src/temp/record/equals.ts deleted file mode 100644 index 07addff1..00000000 --- a/packages/schema-generator/src/temp/record/equals.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Array_isArray, Object_is, Object_keys, Object_hasOwn } from '@traversable/registry' -import type { t } from '@traversable/schema-core' - -export type equals = never | Equal -export function equals(recordSchema: t.record): equals -export function equals(recordSchema: t.record): equals -export function equals({ def }: t.record<{ equals: Equal }>): Equal> { - function recordEquals(l: Record, r: Record): boolean { - if (Object_is(l, r)) return true - if (!l || typeof l !== 'object' || Array_isArray(l)) return false - if (!r || typeof r !== 'object' || Array_isArray(r)) return false - const lhs = Object_keys(l) - const rhs = Object_keys(r) - let len = lhs.length - let k: string - if (len !== rhs.length) return false - for (let ix = len; ix-- !== 0;) { - k = lhs[ix] - if (!Object_hasOwn(r, k)) return false - if (!(def.equals(l[k], r[k]))) return false - } - len = rhs.length - for (let ix = len; ix-- !== 0;) { - k = rhs[ix] - if (!Object_hasOwn(l, k)) return false - if (!(def.equals(l[k], r[k]))) return false - } - return true - } - return recordEquals -} diff --git a/packages/schema-generator/src/temp/record/extension.ts b/packages/schema-generator/src/temp/record/extension.ts deleted file mode 100644 index ee871114..00000000 --- a/packages/schema-generator/src/temp/record/extension.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = {} - -export let Extensions = { - toString, - equals, - toJsonSchema, - validate, -} diff --git a/packages/schema-generator/src/temp/record/toJsonSchema.ts b/packages/schema-generator/src/temp/record/toJsonSchema.ts deleted file mode 100644 index 2503d568..00000000 --- a/packages/schema-generator/src/temp/record/toJsonSchema.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { t } from '@traversable/schema-core' -import type * as T from '@traversable/registry' -import { getSchema } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): { - type: 'object' - additionalProperties: T.Returns - } -} - -export function toJsonSchema(recordSchema: t.record): toJsonSchema -export function toJsonSchema(recordSchema: t.record): toJsonSchema -export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { - return function recordToJsonSchema() { - return { - type: 'object' as const, - additionalProperties: getSchema(def), - } - } -} diff --git a/packages/schema-generator/src/temp/record/toString.ts b/packages/schema-generator/src/temp/record/toString.ts deleted file mode 100644 index 868f3544..00000000 --- a/packages/schema-generator/src/temp/record/toString.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Returns } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { callToString } from '@traversable/schema-to-string' - -export interface toString { - /* @ts-expect-error */ - (): never | `Record}>` -} - -export function toString>(recordSchema: S): toString -export function toString(recordSchema: t.record): toString -export function toString({ def }: { def: unknown }): () => string { - function recordToString() { - return `Record` - } - return recordToString -} diff --git a/packages/schema-generator/src/temp/record/validate.ts b/packages/schema-generator/src/temp/record/validate.ts deleted file mode 100644 index 6d958004..00000000 --- a/packages/schema-generator/src/temp/record/validate.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { Array_isArray, Object_keys, URI } from '@traversable/registry' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = never | ValidationFn -export function validate(recordSchema: t.record): validate -export function validate(recordSchema: t.record): validate -export function validate({ def: { validate = () => true } }: t.record) { - validateRecord.tag = URI.record - function validateRecord(u: unknown, path = Array.of()) { - if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] - let errors = Array.of() - let keys = Object_keys(u) - for (let k of keys) { - let y = u[k] - let results = validate(y, [...path, k]) - if (results === true) continue - else errors.push(...results) - } - return errors.length === 0 || errors - } - return validateRecord -} diff --git a/packages/schema-generator/src/temp/string/core.ts b/packages/schema-generator/src/temp/string/core.ts deleted file mode 100644 index 4276ca17..00000000 --- a/packages/schema-generator/src/temp/string/core.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { Bounds, Integer, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_min, - Math_max, - Object_assign, - URI, - within, -} from '@traversable/registry' - -export { string_ as string } - -/** @internal */ -function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ -function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ -function boundedString(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedStringSchema(u: unknown) { - return string_(u) && within(bounds)(u.length) - }, carry, string_) -} - -interface string_ extends string_.core { - //<%= Types %> -} - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -function StringSchema(src: unknown) { return typeof src === 'string' } -StringSchema.tag = URI.string -StringSchema.def = '' - -const string_ = Object_assign( - StringSchema, - userDefinitions, -) as string_ - -string_.min = function stringMinLength(minLength) { - return Object_assign( - boundedString({ gte: minLength }, carryover(this, 'minLength')), - { minLength }, - ) -} -string_.max = function stringMaxLength(maxLength) { - return Object_assign( - boundedString({ lte: maxLength }, carryover(this, 'maxLength')), - { maxLength }, - ) -} -string_.between = function stringBetween( - min, - max, - minLength = Math_min(min, max), - maxLength = Math_max(min, max)) { - return Object_assign( - boundedString({ gte: minLength, lte: maxLength }), - { minLength, maxLength }, - ) -} - -Object_assign( - string_, - bindUserExtensions(string_, userExtensions), -) - -declare namespace string_ { - interface core extends string_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: string - tag: URI.string - get def(): this['_type'] - } - interface methods { - minLength?: number - maxLength?: number - min>(minLength: Min): string_.Min - max>(maxLength: Max): string_.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ maxLength: number }] - ? string_.between<[min: Min, max: Self['maxLength']]> - : string_.min - ; - type Max - = [Self] extends [{ minLength: number }] - ? string_.between<[min: Self['minLength'], max: Max]> - : string_.max - ; - interface min extends string_ { minLength: Min } - interface max extends string_ { maxLength: Max } - interface between extends string_ { - minLength: Bounds[0] - maxLength: Bounds[1] - } -} diff --git a/packages/schema-generator/src/temp/string/equals.ts b/packages/schema-generator/src/temp/string/equals.ts deleted file mode 100644 index b9444108..00000000 --- a/packages/schema-generator/src/temp/string/equals.ts +++ /dev/null @@ -1,6 +0,0 @@ -import type { Equal } from '@traversable/registry' - -export type equals = Equal -export function equals(left: string, right: string): boolean { - return left === right -} diff --git a/packages/schema-generator/src/temp/string/extension.ts b/packages/schema-generator/src/temp/string/extension.ts deleted file mode 100644 index c64c1266..00000000 --- a/packages/schema-generator/src/temp/string/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - toString, - equals, -} - -export let Extensions = { - toJsonSchema, - validate, -} diff --git a/packages/schema-generator/src/temp/string/toJsonSchema.ts b/packages/schema-generator/src/temp/string/toJsonSchema.ts deleted file mode 100644 index 2956c069..00000000 --- a/packages/schema-generator/src/temp/string/toJsonSchema.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { Force, PickIfDefined } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { has } from '@traversable/registry' -import type { SizeBounds } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): Force<{ type: 'string' } & PickIfDefined> -} - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: t.string): () => { type: 'string' } & Partial { - function stringToJsonSchema() { - const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null - const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null - let out: { type: 'string' } & Partial = { type: 'string' } - minLength !== null && void (out.minLength = minLength) - maxLength !== null && void (out.maxLength = maxLength) - - return out - } - return stringToJsonSchema -} diff --git a/packages/schema-generator/src/temp/string/toString.ts b/packages/schema-generator/src/temp/string/toString.ts deleted file mode 100644 index 86a98e16..00000000 --- a/packages/schema-generator/src/temp/string/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'string' } -export function toString(): 'string' { return 'string' } diff --git a/packages/schema-generator/src/temp/string/validate.ts b/packages/schema-generator/src/temp/string/validate.ts deleted file mode 100644 index 650ce686..00000000 --- a/packages/schema-generator/src/temp/string/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(stringSchema: S): validate { - validateString.tag = URI.string - function validateString(u: unknown, path = Array.of()): true | ValidationError[] { - return stringSchema(u) || [NullaryErrors.number(u, path)] - } - return validateString -} diff --git a/packages/schema-generator/src/temp/symbol/core.ts b/packages/schema-generator/src/temp/symbol/core.ts deleted file mode 100644 index 6e192b3e..00000000 --- a/packages/schema-generator/src/temp/symbol/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { symbol_ as symbol } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface symbol_ extends symbol_.core { - //<%= Types %> -} - -function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } -SymbolSchema.tag = URI.symbol -SymbolSchema.def = Symbol() - -const symbol_ = Object_assign( - SymbolSchema, - userDefinitions, -) as symbol_ - -Object_assign(symbol_, userExtensions) - -declare namespace symbol_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.symbol - _type: symbol - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/temp/symbol/equals.ts b/packages/schema-generator/src/temp/symbol/equals.ts deleted file mode 100644 index f3bb7486..00000000 --- a/packages/schema-generator/src/temp/symbol/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: symbol, right: symbol): boolean { - return Object_is(left, right) -} diff --git a/packages/schema-generator/src/temp/symbol/extension.ts b/packages/schema-generator/src/temp/symbol/extension.ts deleted file mode 100644 index 7a2b8c12..00000000 --- a/packages/schema-generator/src/temp/symbol/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/symbol/toJsonSchema.ts b/packages/schema-generator/src/temp/symbol/toJsonSchema.ts deleted file mode 100644 index 7046b08e..00000000 --- a/packages/schema-generator/src/temp/symbol/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function symbolToJsonSchema() { return void 0 } - return symbolToJsonSchema -} diff --git a/packages/schema-generator/src/temp/symbol/toString.ts b/packages/schema-generator/src/temp/symbol/toString.ts deleted file mode 100644 index 5651fe27..00000000 --- a/packages/schema-generator/src/temp/symbol/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'symbol' } -export function toString(): 'symbol' { return 'symbol' } diff --git a/packages/schema-generator/src/temp/symbol/validate.ts b/packages/schema-generator/src/temp/symbol/validate.ts deleted file mode 100644 index 302e11f2..00000000 --- a/packages/schema-generator/src/temp/symbol/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(symbolSchema: t.symbol): validate { - validateSymbol.tag = URI.symbol - function validateSymbol(u: unknown, path = Array.of()) { - return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] - } - return validateSymbol -} diff --git a/packages/schema-generator/src/temp/tuple/core.ts b/packages/schema-generator/src/temp/tuple/core.ts deleted file mode 100644 index f2b9f6bb..00000000 --- a/packages/schema-generator/src/temp/tuple/core.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { - SchemaOptions as Options, - TypeError, - Unknown -} from '@traversable/registry' - -import { - Array_isArray, - bindUserExtensions, - getConfig, - has, - _isPredicate, - Object_assign, - parseArgs, - symbol, - tuple as tuple$, - URI, -} from '@traversable/registry' - -import type { - Entry, - FirstOptionalItem, - invalid, - Schema, - SchemaLike, - TupleType, - ValidateTuple -} from '../namespace.js' - -import type { optional } from './optional.js' - -export { tuple } - -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> -function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { - return tuple.def(...parseArgs(getConfig().schema, args)) -} - -interface tuple extends tuple.core { - //<%= Types %> -} - -namespace tuple { - export let userDefinitions: Record = { - //<%= Definitions %> - } as tuple - export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple - /* v8 ignore next 1 */ - export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { - let userExtensions: Record = { - //<%= Extensions %> - } - const opt = opt_ || xs.findIndex(has(symbol.optional)) - const options = { - ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) - } satisfies tuple.InternalOptions - const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) - function TupleSchema(src: unknown) { return predicate(src) } - TupleSchema.tag = URI.tuple - TupleSchema.def = xs - TupleSchema.opt = opt - Object_assign(TupleSchema, tuple.userDefinitions) - return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) - } -} - -declare namespace tuple { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.tuple - _type: TupleType - opt: FirstOptionalItem - def: S - } - type type> = never | T - type InternalOptions = { minLength?: number } - type validate = ValidateTuple> - - type from - = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T -} diff --git a/packages/schema-generator/src/temp/tuple/equals.ts b/packages/schema-generator/src/temp/tuple/equals.ts deleted file mode 100644 index 66adadaa..00000000 --- a/packages/schema-generator/src/temp/tuple/equals.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Array_isArray, Object_hasOwn, Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' - -export type equals = Equal - -export function equals(tupleSchema: t.tuple): equals -export function equals(tupleSchema: t.tuple): equals -export function equals(tupleSchema: t.tuple) { - function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { - if (Object_is(l, r)) return true - if (Array_isArray(l)) { - if (!Array_isArray(r)) return false - for (let ix = tupleSchema.def.length; ix-- !== 0;) { - if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue - if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false - if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false - if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { - if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false - } - } - return true - } - return false - } - return tupleEquals -} diff --git a/packages/schema-generator/src/temp/tuple/extension.ts b/packages/schema-generator/src/temp/tuple/extension.ts deleted file mode 100644 index ee871114..00000000 --- a/packages/schema-generator/src/temp/tuple/extension.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = {} - -export let Extensions = { - toString, - equals, - toJsonSchema, - validate, -} diff --git a/packages/schema-generator/src/temp/tuple/toJsonSchema.ts b/packages/schema-generator/src/temp/tuple/toJsonSchema.ts deleted file mode 100644 index a71ee145..00000000 --- a/packages/schema-generator/src/temp/tuple/toJsonSchema.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { Returns } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' -import type { MinItems } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): { - type: 'array', - items: { [I in keyof T]: Returns } - additionalItems: false - minItems: MinItems - maxItems: T['length' & keyof T] - } -} - -export function toJsonSchema(tupleSchema: t.tuple): toJsonSchema -export function toJsonSchema({ def }: t.tuple): () => { - type: 'array' - items: unknown - additionalItems: false - minItems?: {} - maxItems?: number -} { - function tupleToJsonSchema() { - let min = minItems(def) - let max = def.length - let items = applyTupleOptionality(def, { min, max }) - return { - type: 'array' as const, - additionalItems: false as const, - items, - minItems: min, - maxItems: max, - } - } - return tupleToJsonSchema -} diff --git a/packages/schema-generator/src/temp/tuple/toString.ts b/packages/schema-generator/src/temp/tuple/toString.ts deleted file mode 100644 index 638daca9..00000000 --- a/packages/schema-generator/src/temp/tuple/toString.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { Join } from '@traversable/registry' -import { Array_isArray, has, URI } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { hasToString } from '@traversable/schema-to-string' - -export interface toString { - (): never | `[${Join<{ - [I in keyof T]: `${ - /* @ts-expect-error */ - T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType - }` - }, ', '>}]` -} - -export function toString(tupleSchema: t.tuple): toString -export function toString(tupleSchema: t.tuple): () => string { - let isOptional = has('tag', (tag) => tag === URI.optional) - function stringToString() { - return Array_isArray(tupleSchema.def) - ? `[${tupleSchema.def.map( - (x) => isOptional(x) - ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` - : hasToString(x) ? x.toString() : 'unknown' - ).join(', ')}]` : 'unknown[]' - } - return stringToString -} diff --git a/packages/schema-generator/src/temp/tuple/validate.ts b/packages/schema-generator/src/temp/tuple/validate.ts deleted file mode 100644 index 5b065453..00000000 --- a/packages/schema-generator/src/temp/tuple/validate.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Array_isArray, has, URI } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' -import { Errors } from '@traversable/derive-validators' - -export type validate = Validate -export function validate(tupleSchema: t.tuple<[...S]>): validate -export function validate(tupleSchema: t.tuple<[...S]>): validate -export function validate(tupleSchema: t.tuple<[...S]>): Validate { - validateTuple.tag = URI.tuple - let isOptional = has('tag', (tag) => tag === URI.optional) - function validateTuple(u: unknown, path = Array.of()) { - let errors = Array.of() - if (!Array_isArray(u)) return [Errors.array(u, path)] - for (let i = 0; i < tupleSchema.def.length; i++) { - if (!(i in u) && !(isOptional(tupleSchema.def[i].validate))) { - errors.push(Errors.missingIndex(u, [...path, i])) - continue - } - let results = tupleSchema.def[i].validate(u[i], [...path, i]) - if (results !== true) { - for (let j = 0; j < results.length; j++) errors.push(results[j]) - results.push(Errors.arrayElement(u[i], [...path, i])) - } - } - if (u.length > tupleSchema.def.length) { - for (let k = tupleSchema.def.length; k < u.length; k++) { - let excess = u[k] - errors.push(Errors.excessItems(excess, [...path, k])) - } - } - return errors.length === 0 || errors - } - return validateTuple -} diff --git a/packages/schema-generator/src/temp/undefined/core.ts b/packages/schema-generator/src/temp/undefined/core.ts deleted file mode 100644 index 0115f168..00000000 --- a/packages/schema-generator/src/temp/undefined/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { undefined_ as undefined } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface undefined_ extends undefined_.core { - //<%= Types %> -} - -function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } -UndefinedSchema.tag = URI.undefined -UndefinedSchema.def = void 0 as undefined - -const undefined_ = Object_assign( - UndefinedSchema, - userDefinitions, -) as undefined_ - -Object_assign(undefined_, userExtensions) - -declare namespace undefined_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.undefined - _type: undefined - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/temp/undefined/equals.ts b/packages/schema-generator/src/temp/undefined/equals.ts deleted file mode 100644 index 75156d56..00000000 --- a/packages/schema-generator/src/temp/undefined/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: undefined, right: undefined): boolean { - return Object_is(left, right) -} diff --git a/packages/schema-generator/src/temp/undefined/extension.ts b/packages/schema-generator/src/temp/undefined/extension.ts deleted file mode 100644 index 7a2b8c12..00000000 --- a/packages/schema-generator/src/temp/undefined/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/undefined/toJsonSchema.ts b/packages/schema-generator/src/temp/undefined/toJsonSchema.ts deleted file mode 100644 index be46c306..00000000 --- a/packages/schema-generator/src/temp/undefined/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function undefinedToJsonSchema(): void { return void 0 } - return undefinedToJsonSchema -} diff --git a/packages/schema-generator/src/temp/undefined/toString.ts b/packages/schema-generator/src/temp/undefined/toString.ts deleted file mode 100644 index a48b744b..00000000 --- a/packages/schema-generator/src/temp/undefined/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'undefined' } -export function toString(): 'undefined' { return 'undefined' } diff --git a/packages/schema-generator/src/temp/undefined/validate.ts b/packages/schema-generator/src/temp/undefined/validate.ts deleted file mode 100644 index d69c9d9e..00000000 --- a/packages/schema-generator/src/temp/undefined/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(undefinedSchema: t.undefined): validate { - validateUndefined.tag = URI.undefined - function validateUndefined(u: unknown, path = Array.of()) { - return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] - } - return validateUndefined -} diff --git a/packages/schema-generator/src/temp/union/core.ts b/packages/schema-generator/src/temp/union/core.ts deleted file mode 100644 index 1e3705ee..00000000 --- a/packages/schema-generator/src/temp/union/core.ts +++ /dev/null @@ -1,50 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { - _isPredicate, - bindUserExtensions, - isUnknown as isAny, - Object_assign, - union as union$, - URI, -} from '@traversable/registry' - -import type { Entry, Schema, SchemaLike } from '../namespace.js' - -export function union(...schemas: S): union -export function union }>(...schemas: S): union -export function union(...schemas: unknown[]) { - return union.def(schemas) -} - -export interface union extends union.core { - //<%= Types %> -} - -export namespace union { - export let userDefinitions: Record = { - //<%= Definitions %> - } as Partial> - export function def(xs: T): union - /* v8 ignore next 1 */ - export function def(xs: unknown[]) { - let userExtensions: Record = { - //<%= Extensions %> - } - const predicate = xs.every(_isPredicate) ? union$(xs) : isAny - function UnionSchema(src: unknown): src is unknown { return predicate(src) } - UnionSchema.tag = URI.union - UnionSchema.def = xs - Object_assign(UnionSchema, union.userDefinitions) - return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) - } -} - -export declare namespace union { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.union - _type: union.type - get def(): S - } - type type = never | T -} diff --git a/packages/schema-generator/src/temp/union/equals.ts b/packages/schema-generator/src/temp/union/equals.ts deleted file mode 100644 index c0275e69..00000000 --- a/packages/schema-generator/src/temp/union/equals.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' -import type { t } from '@traversable/schema-core' - -export type equals = Equal -export function equals(unionSchema: t.union<[...S]>): equals -export function equals(unionSchema: t.union<[...S]>): equals -export function equals({ def }: t.union<{ equals: Equal }[]>): Equal { - function unionEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - for (let ix = def.length; ix-- !== 0;) - if (def[ix].equals(l, r)) return true - return false - } - return unionEquals -} diff --git a/packages/schema-generator/src/temp/union/extension.ts b/packages/schema-generator/src/temp/union/extension.ts deleted file mode 100644 index ee871114..00000000 --- a/packages/schema-generator/src/temp/union/extension.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = {} - -export let Extensions = { - toString, - equals, - toJsonSchema, - validate, -} diff --git a/packages/schema-generator/src/temp/union/toJsonSchema.ts b/packages/schema-generator/src/temp/union/toJsonSchema.ts deleted file mode 100644 index f9467612..00000000 --- a/packages/schema-generator/src/temp/union/toJsonSchema.ts +++ /dev/null @@ -1,17 +0,0 @@ -import type { Returns } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { getSchema } from '@traversable/schema-to-json-schema' - -export interface toJsonSchema { - (): { anyOf: { [I in keyof T]: Returns } } -} - -export function toJsonSchema(unionSchema: t.union): toJsonSchema -export function toJsonSchema(unionSchema: t.union): toJsonSchema -export function toJsonSchema({ def }: t.union): () => {} { - return function unionToJsonSchema() { - return { - anyOf: def.map(getSchema) - } - } -} diff --git a/packages/schema-generator/src/temp/union/toString.ts b/packages/schema-generator/src/temp/union/toString.ts deleted file mode 100644 index d9c0f3d1..00000000 --- a/packages/schema-generator/src/temp/union/toString.ts +++ /dev/null @@ -1,18 +0,0 @@ -import type { Join } from '@traversable/registry' -import { Array_isArray } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import { callToString } from '@traversable/schema-to-string' - -export interface toString { - (): never | [T] extends [readonly []] ? 'never' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` -} - -export function toString(unionSchema: t.union): toString -export function toString({ def }: t.union): () => string { - function unionToString() { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' - } - return unionToString -} diff --git a/packages/schema-generator/src/temp/union/validate.ts b/packages/schema-generator/src/temp/union/validate.ts deleted file mode 100644 index 95b1f0f0..00000000 --- a/packages/schema-generator/src/temp/union/validate.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { URI } from '@traversable/registry' -import type { t } from '@traversable/schema-core' -import type { ValidationError, Validate, Validator } from '@traversable/derive-validators' - -export type validate = Validate - -export function validate(unionSchema: t.union): validate -export function validate(unionSchema: t.union): validate -export function validate({ def }: t.union) { - validateUnion.tag = URI.union - function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { - // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; - let errors = Array.of() - for (let i = 0; i < def.length; i++) { - let results = def[i].validate(u, path) - if (results === true) { - // validateUnion.optional = 0 - return true - } - for (let j = 0; j < results.length; j++) errors.push(results[j]) - } - // validateUnion.optional = 0 - return errors.length === 0 || errors - } - return validateUnion -} diff --git a/packages/schema-generator/src/temp/unknown/core.ts b/packages/schema-generator/src/temp/unknown/core.ts deleted file mode 100644 index 0a190db3..00000000 --- a/packages/schema-generator/src/temp/unknown/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { unknown_ as unknown } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface unknown_ extends unknown_.core { - //<%= Types %> -} - -function UnknownSchema(src: unknown): src is unknown { return true } -UnknownSchema.tag = URI.unknown -UnknownSchema.def = void 0 as unknown - -const unknown_ = Object_assign( - UnknownSchema, - userDefinitions, -) as unknown_ - -Object_assign(unknown_, userExtensions) - -declare namespace unknown_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.unknown - _type: unknown - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/temp/unknown/equals.ts b/packages/schema-generator/src/temp/unknown/equals.ts deleted file mode 100644 index ccd2a780..00000000 --- a/packages/schema-generator/src/temp/unknown/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: any, right: any): boolean { - return Object_is(left, right) -} diff --git a/packages/schema-generator/src/temp/unknown/extension.ts b/packages/schema-generator/src/temp/unknown/extension.ts deleted file mode 100644 index 7a2b8c12..00000000 --- a/packages/schema-generator/src/temp/unknown/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/unknown/toJsonSchema.ts b/packages/schema-generator/src/temp/unknown/toJsonSchema.ts deleted file mode 100644 index 8d5be5a0..00000000 --- a/packages/schema-generator/src/temp/unknown/toJsonSchema.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } -export function toJsonSchema(): toJsonSchema { - function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } - return anyToJsonSchema -} diff --git a/packages/schema-generator/src/temp/unknown/toString.ts b/packages/schema-generator/src/temp/unknown/toString.ts deleted file mode 100644 index 417e1048..00000000 --- a/packages/schema-generator/src/temp/unknown/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'unknown' } -export function toString(): 'unknown' { return 'unknown' } diff --git a/packages/schema-generator/src/temp/unknown/validate.ts b/packages/schema-generator/src/temp/unknown/validate.ts deleted file mode 100644 index 9c4298c0..00000000 --- a/packages/schema-generator/src/temp/unknown/validate.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(_?: t.unknown): validate { - validateUnknown.tag = URI.unknown - function validateUnknown() { return true as const } - return validateUnknown -} diff --git a/packages/schema-generator/src/temp/void/core.ts b/packages/schema-generator/src/temp/void/core.ts deleted file mode 100644 index d178213e..00000000 --- a/packages/schema-generator/src/temp/void/core.ts +++ /dev/null @@ -1,36 +0,0 @@ -import type { Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' - -export { void_ as void, void_ } - -export let userDefinitions: Record = { - //<%= Definitions %> -} - -export let userExtensions: Record = { - //<%= Extensions %> -} - -interface void_ extends void_.core { - //<%= Types %> -} - -function VoidSchema(src: unknown): src is void { return src === void 0 } -VoidSchema.tag = URI.void -VoidSchema.def = void 0 as void - -const void_ = Object_assign( - VoidSchema, - userDefinitions, -) as void_ - -Object_assign(void_, userExtensions) - -declare namespace void_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.void - _type: void - get def(): this['_type'] - } -} diff --git a/packages/schema-generator/src/temp/void/equals.ts b/packages/schema-generator/src/temp/void/equals.ts deleted file mode 100644 index d11d89e3..00000000 --- a/packages/schema-generator/src/temp/void/equals.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Equal } from '@traversable/registry' -import { Object_is } from '@traversable/registry' - -export type equals = Equal -export function equals(left: void, right: void): boolean { - return Object_is(left, right) -} diff --git a/packages/schema-generator/src/temp/void/extension.ts b/packages/schema-generator/src/temp/void/extension.ts deleted file mode 100644 index 7a2b8c12..00000000 --- a/packages/schema-generator/src/temp/void/extension.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { equals } from './equals.js' -import { toJsonSchema } from './toJsonSchema.js' -import { toString } from './toString.js' -import { validate } from './validate.js' - -export interface Types { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let Definitions = { - equals, - toJsonSchema, - toString, -} - -export let Extensions = { - validate, -} diff --git a/packages/schema-generator/src/temp/void/toJsonSchema.ts b/packages/schema-generator/src/temp/void/toJsonSchema.ts deleted file mode 100644 index d636b569..00000000 --- a/packages/schema-generator/src/temp/void/toJsonSchema.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function voidToJsonSchema(): void { - return void 0 - } - return voidToJsonSchema -} diff --git a/packages/schema-generator/src/temp/void/toString.ts b/packages/schema-generator/src/temp/void/toString.ts deleted file mode 100644 index 487d08b3..00000000 --- a/packages/schema-generator/src/temp/void/toString.ts +++ /dev/null @@ -1,2 +0,0 @@ -export interface toString { (): 'void' } -export function toString(): 'void' { return 'void' } diff --git a/packages/schema-generator/src/temp/void/validate.ts b/packages/schema-generator/src/temp/void/validate.ts deleted file mode 100644 index a67fc4e4..00000000 --- a/packages/schema-generator/src/temp/void/validate.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { t } from '@traversable/schema-core' -import { URI } from '@traversable/registry' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - -export type validate = ValidationFn -export function validate(voidSchema: t.void): validate { - validateVoid.tag = URI.void - function validateVoid(u: unknown, path = Array.of()) { - return voidSchema(u) || [NullaryErrors.void(u, path)] - } - return validateVoid -} diff --git a/packages/schema-generator/test/generate.test.ts b/packages/schema-generator/test/generate.test.ts index aeae3875..2885e6bc 100644 --- a/packages/schema-generator/test/generate.test.ts +++ b/packages/schema-generator/test/generate.test.ts @@ -212,7 +212,7 @@ let PATH = { } } -vi.describe('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { +vi.describe.skip('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', () => { vi.it('〖️⛳️〗› ❲writeSchemas❳', () => { if (!fs.existsSync(DIR_PATH)) fs.mkdirSync(DIR_PATH) if (!fs.existsSync(DATA_PATH)) fs.mkdirSync(DATA_PATH) diff --git a/packages/schema-generator/test/test-data/object/equals.ts b/packages/schema-generator/test/test-data/object/equals.ts index 3cd8134f..1e91cfbc 100644 --- a/packages/schema-generator/test/test-data/object/equals.ts +++ b/packages/schema-generator/test/test-data/object/equals.ts @@ -5,7 +5,7 @@ import type { t } from '@traversable/schema-core' export type equals = never | T.Equal export function equals(objectSchema: t.object): equals> export function equals(objectSchema: t.object): equals> -export function equals({ def }: t.object<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { +export function equals({ def }: t.object): equals> { function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { if (Object_is(l, r)) return true if (!l || typeof l !== 'object' || Array_isArray(l)) return false diff --git a/packages/schema-generator/test/test-data/object/validate.ts b/packages/schema-generator/test/test-data/object/validate.ts index 8d66e417..da8e64f1 100644 --- a/packages/schema-generator/test/test-data/object/validate.ts +++ b/packages/schema-generator/test/test-data/object/validate.ts @@ -25,7 +25,7 @@ export type validate = never | ValidationFn export function validate(objectSchema: t.object): validate export function validate(objectSchema: t.object): validate -export function validate(objectSchema: t.object<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { +export function validate(objectSchema: t.object): validate<{ [x: string]: unknown }> { validateObject.tag = URI.object function validateObject(u: unknown, path_ = Array.of()) { // if (objectSchema(u)) return true diff --git a/packages/schema-generator/tsconfig.test.json b/packages/schema-generator/tsconfig.test.json index 7277a1a7..21695d87 100644 --- a/packages/schema-generator/tsconfig.test.json +++ b/packages/schema-generator/tsconfig.test.json @@ -7,6 +7,7 @@ "noEmit": true }, "references": [ + { "path": "tsconfig.src.json" }, { "path": "../derive-validators" }, { "path": "../derive-equals" }, { "path": "../registry" }, diff --git a/packages/schema/src/__schemas__/any.ts b/packages/schema/src/__schemas__/any.ts deleted file mode 100644 index 7ada302b..00000000 --- a/packages/schema/src/__schemas__/any.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * any_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: unknown, right: unknown): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } -export function toJsonSchema(): toJsonSchema { - function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } - return unknownToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'any' } -export function toString(): 'any' { return 'any' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(_?: any_): validate { - validateAny.tag = URI.any - function validateAny() { return true as const } - return validateAny -} -/// validate /// -////////////////////// - -export { any_ as any } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface any_ extends any_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function AnySchema(src: unknown): src is any { return true } -AnySchema.tag = URI.any -AnySchema.def = void 0 as any - -const any_ = Object_assign( - AnySchema, - userDefinitions, -) as any_ - -Object_assign(any_, userExtensions) - -declare namespace any_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.any - _type: any - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/array.ts b/packages/schema/src/__schemas__/array.ts deleted file mode 100644 index eed1464d..00000000 --- a/packages/schema/src/__schemas__/array.ts +++ /dev/null @@ -1,250 +0,0 @@ -/** - * array schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type * as T from '@traversable/registry' -import type { - Bounds, - Equal, - Integer, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - array as arrayOf, - Array_isArray, - bindUserExtensions, - carryover, - has, - Math_max, - Math_min, - Number_isSafeInteger, - Object_assign, - Object_is, - URI, - within -} from '@traversable/registry' -import type { Guarded, Schema, SchemaLike } from '../_namespace.js' -import type { of } from './of.js' -import type { t } from '../_exports.js' -import type { SizeBounds } from '@traversable/schema-to-json-schema' -import { hasSchema } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { Errors, NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | Equal - -export function equals(arraySchema: array): equals -export function equals(arraySchema: array): equals -export function equals({ def }: array<{ equals: Equal }>): Equal { - let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is - function arrayEquals(l: unknown[], r: unknown[]): boolean { - if (Object_is(l, r)) return true - if (Array_isArray(l)) { - if (!Array_isArray(r)) return false - let len = l.length - if (len !== r.length) return false - for (let ix = len; ix-- !== 0;) - if (!equals(l[ix], r[ix])) return false - return true - } else return false - } - return arrayEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): never | T.Force< - & { type: 'array', items: T.Returns } - & T.PickIfDefined - > -} - -export function toJsonSchema>(arraySchema: T): toJsonSchema -export function toJsonSchema(arraySchema: T): toJsonSchema -export function toJsonSchema( - { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, -): () => { - type: 'array' - items: unknown - minLength?: number - maxLength?: number -} { - function arrayToJsonSchema() { - let items = hasSchema(def) ? def.toJsonSchema() : def - let out = { - type: 'array' as const, - items, - minLength, - maxLength, - } - if (typeof minLength !== 'number') delete out.minLength - if (typeof maxLength !== 'number') delete out.maxLength - return out - } - return arrayToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - /* @ts-expect-error */ - (): never | `(${ReturnType})[]` -} - -export function toString(arraySchema: array): toString -export function toString(arraySchema: array): toString -export function toString({ def }: { def: unknown }) { - function arrayToString() { - let body = ( - !!def - && typeof def === 'object' - && 'toString' in def - && typeof def.toString === 'function' - ) ? def.toString() - : '${string}' - return ('(' + body + ')[]') - } - return arrayToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = never | ValidationFn -export function validate(arraySchema: array): validate -export function validate(arraySchema: array): validate -export function validate( - { def: { validate = () => true }, minLength, maxLength }: array -) { - validateArray.tag = URI.array - function validateArray(u: unknown, path = Array.of()) { - if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] - let errors = Array.of() - if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) - if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) - for (let i = 0, len = u.length; i < len; i++) { - let y = u[i] - let results = validate(y, [...path, i]) - if (results === true) continue - else errors.push(...results) - } - return errors.length === 0 || errors - } - return validateArray -} -/// validate /// -////////////////////// - -/** @internal */ -function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array -function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { - return Object_assign(function BoundedArraySchema(u: unknown) { - return Array_isArray(u) && within(bounds)(u.length) - }, carry, array(schema)) -} - -export interface array extends array.core { - toString: toString - equals: equals - toJsonSchema: toJsonSchema - validate: validate -} - -export function array(schema: S, readonly: 'readonly'): readonlyArray -export function array(schema: S): array -export function array(schema: S): array>> -export function array(schema: S): array { - return array.def(schema) -} - -export namespace array { - export let userDefinitions: Record = { - } as array - export function def(x: S, prev?: array): array - export function def(x: S, prev?: unknown): array - export function def(x: S, prev?: array): array - export function def(x: unknown, prev?: unknown): {} { - let userExtensions: Record = { - toJsonSchema, - validate, - toString, - equals, - } - const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray - function ArraySchema(src: unknown) { return predicate(src) } - ArraySchema.tag = URI.array - ArraySchema.def = x - ArraySchema.min = function arrayMin(minLength: Min) { - return Object_assign( - boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), - { minLength }, - ) - } - ArraySchema.max = function arrayMax(maxLength: Max) { - return Object_assign( - boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), - { maxLength }, - ) - } - ArraySchema.between = function arrayBetween( - min: Min, - max: Max, - minLength = Math_min(min, max), - maxLength = Math_max(min, max) - ) { - return Object_assign( - boundedArray(x, { gte: minLength, lte: maxLength }), - { minLength, maxLength }, - ) - } - if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength - if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength - Object_assign(ArraySchema, userDefinitions) - return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) - } -} - -export declare namespace array { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.array - get def(): S - _type: S['_type' & keyof S][] - minLength?: number - maxLength?: number - min>(minLength: Min): array.Min - max>(maxLength: Max): array.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> - } - type Min - = [Self] extends [{ maxLength: number }] - ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> - : array.min - ; - type Max - = [Self] extends [{ minLength: number }] - ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> - : array.max - ; - interface min extends array { minLength: Min } - interface max extends array { maxLength: Max } - interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } - type type = never | T -} - -export const readonlyArray: { - (schema: S): readonlyArray - (schema: S): readonlyArray> -} = array -export interface readonlyArray { - (u: unknown): u is this['_type'] - tag: URI.array - def: S - _type: ReadonlyArray -} diff --git a/packages/schema/src/__schemas__/bigint.ts b/packages/schema/src/__schemas__/bigint.ts deleted file mode 100644 index bb4b337e..00000000 --- a/packages/schema/src/__schemas__/bigint.ts +++ /dev/null @@ -1,148 +0,0 @@ -/** - * bigint_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Bounds, Equal, Unknown } from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Object_assign, - Object_is, - URI, - withinBig as within -} from '@traversable/registry' -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' -import type { t } from '../_exports.js' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: bigint, right: bigint): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function bigintToJsonSchema(): void { - return void 0 - } - return bigintToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'bigint' } -export function toString(): 'bigint' { return 'bigint' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(bigIntSchema: S): validate { - validateBigInt.tag = URI.bigint - function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { - return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] - } - return validateBigInt -} -/// validate /// -////////////////////// - -export { bigint_ as bigint } - -/** @internal */ -function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ -function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ -function boundedBigInt(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedBigIntSchema(u: unknown) { - return bigint_(u) && within(bounds)(u) - }, carry, bigint_) -} - -interface bigint_ extends bigint_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -function BigIntSchema(src: unknown) { return typeof src === 'bigint' } -BigIntSchema.tag = URI.bigint -BigIntSchema.def = 0n - -const bigint_ = Object_assign( - BigIntSchema, - userDefinitions, -) as bigint_ - -bigint_.min = function bigIntMin(minimum) { - return Object_assign( - boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -bigint_.max = function bigIntMax(maximum) { - return Object_assign( - boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -bigint_.between = function bigIntBetween( - min, - max, - minimum = (max < min ? max : min), - maximum = (max < min ? min : max), -) { - return Object_assign( - boundedBigInt({ gte: minimum, lte: maximum }), - { minimum, maximum } - ) -} - -Object_assign( - bigint_, - bindUserExtensions(bigint_, userExtensions), -) - -declare namespace bigint_ { - interface core extends bigint_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: bigint - tag: URI.bigint - get def(): this['_type'] - minimum?: bigint - maximum?: bigint - } - type Min - = [Self] extends [{ maximum: bigint }] - ? bigint_.between<[min: X, max: Self['maximum']]> - : bigint_.min - ; - type Max - = [Self] extends [{ minimum: bigint }] - ? bigint_.between<[min: Self['minimum'], max: X]> - : bigint_.max - ; - interface methods { - min(minimum: Min): bigint_.Min - max(maximum: Max): bigint_.Max - between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> - } - interface min extends bigint_ { minimum: Min } - interface max extends bigint_ { maximum: Max } - interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } -} diff --git a/packages/schema/src/__schemas__/boolean.ts b/packages/schema/src/__schemas__/boolean.ts deleted file mode 100644 index ebbaac60..00000000 --- a/packages/schema/src/__schemas__/boolean.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * boolean_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: boolean, right: boolean): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { type: 'boolean' } } -export function toJsonSchema(): toJsonSchema { - function booleanToJsonSchema() { return { type: 'boolean' as const } } - return booleanToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'boolean' } -export function toString(): 'boolean' { return 'boolean' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(booleanSchema: boolean_): validate { - validateBoolean.tag = URI.boolean - function validateBoolean(u: unknown, path = Array.of()) { - return booleanSchema(true as const) || [NullaryErrors.null(u, path)] - } - return validateBoolean -} -/// validate /// -////////////////////// - -export { boolean_ as boolean } - -interface boolean_ extends boolean_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } - -BooleanSchema.tag = URI.boolean -BooleanSchema.def = false - -const boolean_ = Object_assign( - BooleanSchema, - userDefinitions, -) as boolean_ - -Object_assign(boolean_, userExtensions) - -declare namespace boolean_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.boolean - _type: boolean - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/eq.ts b/packages/schema/src/__schemas__/eq.ts deleted file mode 100644 index eb99ab5f..00000000 --- a/packages/schema/src/__schemas__/eq.ts +++ /dev/null @@ -1,122 +0,0 @@ -/** - * eq schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Key, - Mut, - Mutable, - SchemaOptions as Options, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - applyOptions, - bindUserExtensions, - Equal, - getConfig, - laxEquals, - Object_assign, - URI -} from '@traversable/registry' -import type { t } from '../_exports.js' -import { stringify } from '@traversable/schema-to-string' -import type { Validate } from '@traversable/derive-validators' -import { Errors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | Equal -export function equals(eqSchema: eq): equals -export function equals(): Equal { - return function eqEquals(left: any, right: any) { - return laxEquals(left, right) - } -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { const: T } } -export function toJsonSchema(eqSchema: eq): toJsonSchema -export function toJsonSchema({ def }: eq) { - function eqToJsonSchema() { return { const: def } } - return eqToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - (): [Key] extends [never] - ? [T] extends [symbol] ? 'symbol' : 'symbol' - : [T] extends [string] ? `'${T}'` : Key -} - -export function toString(eqSchema: eq): toString -export function toString({ def }: eq): () => string { - function eqToString(): string { - return typeof def === 'symbol' ? 'symbol' : stringify(def) - } - return eqToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate -export function validate(eqSchema: eq): validate -export function validate({ def }: eq): validate { - validateEq.tag = URI.eq - function validateEq(u: unknown, path = Array.of()) { - let options = getConfig().schema - let equals = options?.eq?.equalsFn || Equal.lax - if (equals(def, u)) return true - else return [Errors.eq(u, path, def)] - } - return validateEq -} -/// validate /// -////////////////////// - -export function eq>(value: V, options?: Options): eq> -export function eq(value: V, options?: Options): eq -export function eq(value: V, options?: Options): eq { - return eq.def(value, options) -} - -export interface eq extends eq.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export namespace eq { - export let userDefinitions: Record = { - } - export function def(value: T, options?: Options): eq - export function def(x: T, $?: Options): {} { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const options = applyOptions($) - const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) - function EqSchema(src: unknown) { return predicate(src) } - EqSchema.tag = URI.eq - EqSchema.def = x - Object_assign(EqSchema, eq.userDefinitions) - return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) - } -} - -export declare namespace eq { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.eq - _type: V - get def(): V - } -} diff --git a/packages/schema/src/__schemas__/integer.ts b/packages/schema/src/__schemas__/integer.ts deleted file mode 100644 index 6594ba9b..00000000 --- a/packages/schema/src/__schemas__/integer.ts +++ /dev/null @@ -1,169 +0,0 @@ -/** - * integer schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Bounds, - Equal, - Force, - Integer, - PickIfDefined, - Unknown -} from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_max, - Math_min, - Number_isSafeInteger, - Object_assign, - SameValueNumber, - URI, - within -} from '@traversable/registry' -import type { t } from '../_exports.js' -import type { NumericBounds } from '@traversable/schema-to-json-schema' -import { getNumericBounds } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: number, right: number): boolean { - return SameValueNumber(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: integer): toJsonSchema { - function integerToJsonSchema() { - const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) - let bounds: NumericBounds = {} - if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum - if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum - if (typeof minimum === 'number') bounds.minimum = minimum - if (typeof maximum === 'number') bounds.maximum = maximum - return { - type: 'integer' as const, - ...bounds, - } - } - return integerToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'number' } -export function toString(): 'number' { return 'number' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(integerSchema: S): validate { - validateInteger.tag = URI.integer - function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { - return integerSchema(u) || [NullaryErrors.integer(u, path)] - } - return validateInteger -} -/// validate /// -////////////////////// - -export { integer } - -/** @internal */ -function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer -function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer -function boundedInteger(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedIntegerSchema(u: unknown) { - return integer(u) && within(bounds)(u) - }, carry, integer) -} - -interface integer extends integer.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let userDefinitions: Record = { - equals, - toString, -} - -export let userExtensions: Record = { - toJsonSchema, - validate, -} - -function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } -IntegerSchema.tag = URI.integer -IntegerSchema.def = 0 - -const integer = Object_assign( - IntegerSchema, - userDefinitions, -) as integer - -integer.min = function integerMin(minimum) { - return Object_assign( - boundedInteger({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -integer.max = function integerMax(maximum) { - return Object_assign( - boundedInteger({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -integer.between = function integerBetween( - min, - max, - minimum = Math_min(min, max), - maximum = Math_max(min, max), -) { - return Object_assign( - boundedInteger({ gte: minimum, lte: maximum }), - { minimum, maximum }, - ) -} - -Object_assign( - integer, - bindUserExtensions(integer, userExtensions), -) - -declare namespace integer { - interface core extends integer.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: number - tag: URI.integer - get def(): this['_type'] - minimum?: number - maximum?: number - } - interface methods { - min>(minimum: Min): integer.Min - max>(maximum: Max): integer.Max - between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ maximum: number }] - ? integer.between<[min: X, max: Self['maximum']]> - : integer.min - type Max - = [Self] extends [{ minimum: number }] - ? integer.between<[min: Self['minimum'], max: X]> - : integer.max - interface min extends integer { minimum: Min } - interface max extends integer { maximum: Max } - interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } -} diff --git a/packages/schema/src/__schemas__/intersect.ts b/packages/schema/src/__schemas__/intersect.ts deleted file mode 100644 index fbc80453..00000000 --- a/packages/schema/src/__schemas__/intersect.ts +++ /dev/null @@ -1,147 +0,0 @@ -/** - * intersect schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Equal, - Join, - Returns, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - Array_isArray, - bindUserExtensions, - intersect as intersect$, - isUnknown as isAny, - Object_assign, - Object_is, - URI -} from '@traversable/registry' -import type { - Entry, - IntersectType, - Schema, - SchemaLike -} from '../_namespace.js' -import type { t } from '../_exports.js' -import { getSchema } from '@traversable/schema-to-json-schema' -import { callToString } from '@traversable/schema-to-string' -import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(intersectSchema: intersect<[...S]>): equals -export function equals(intersectSchema: intersect<[...S]>): equals -export function equals({ def }: intersect<{ equals: Equal }[]>): Equal { - function intersectEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - for (let ix = def.length; ix-- !== 0;) - if (!def[ix].equals(l, r)) return false - return true - } - return intersectEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): { - allOf: { [I in keyof T]: Returns } - } -} - -export function toJsonSchema(intersectSchema: intersect): toJsonSchema -export function toJsonSchema(intersectSchema: intersect): toJsonSchema -export function toJsonSchema({ def }: intersect): () => {} { - function intersectToJsonSchema() { - return { - allOf: def.map(getSchema) - } - } - return intersectToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - (): never | [T] extends [readonly []] ? 'unknown' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` -} - -export function toString(intersectSchema: intersect): toString -export function toString({ def }: intersect): () => string { - function intersectToString() { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' - } - return intersectToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate - -export function validate(intersectSchema: intersect): validate -export function validate(intersectSchema: intersect): validate -export function validate({ def }: intersect) { - validateIntersect.tag = URI.intersect - function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { - let errors = Array.of() - for (let i = 0; i < def.length; i++) { - let results = def[i].validate(u, path) - if (results !== true) - for (let j = 0; j < results.length; j++) errors.push(results[j]) - } - return errors.length === 0 || errors - } - return validateIntersect -} -/// validate /// -////////////////////// - -export function intersect(...schemas: S): intersect -export function intersect }>(...schemas: S): intersect -export function intersect(...schemas: readonly unknown[]) { - return intersect.def(schemas) -} - -export interface intersect extends intersect.core { - toString: toString - equals: equals - toJsonSchema: toJsonSchema - validate: validate -} - -export namespace intersect { - export let userDefinitions: Record = { - } as intersect - export function def(xs: readonly [...T]): intersect - export function def(xs: readonly unknown[]): {} { - let userExtensions: Record = { - toJsonSchema, - validate, - toString, - equals, - } - const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny - function IntersectSchema(src: unknown) { return predicate(src) } - IntersectSchema.tag = URI.intersect - IntersectSchema.def = xs - Object_assign(IntersectSchema, intersect.userDefinitions) - return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) - } -} - -export declare namespace intersect { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.intersect - get def(): S - _type: IntersectType - } - type type> = never | T -} diff --git a/packages/schema/src/__schemas__/never.ts b/packages/schema/src/__schemas__/never.ts deleted file mode 100644 index 25d455a1..00000000 --- a/packages/schema/src/__schemas__/never.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * never_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: never, right: never): boolean { - return false -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): never } -export function toJsonSchema(): toJsonSchema { - function neverToJsonSchema() { return void 0 as never } - return neverToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'never' } -export function toString(): 'never' { return 'never' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(_?: never_): validate { - validateNever.tag = URI.never - function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } - return validateNever -} -/// validate /// -////////////////////// - -export { never_ as never } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface never_ extends never_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function NeverSchema(src: unknown): src is never { return false } -NeverSchema.tag = URI.never; -NeverSchema.def = void 0 as never - -const never_ = Object_assign( - NeverSchema, - userDefinitions, -) as never_ - -Object_assign(never_, userExtensions) - -export declare namespace never_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.never - _type: never - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/null.ts b/packages/schema/src/__schemas__/null.ts deleted file mode 100644 index e9f0a525..00000000 --- a/packages/schema/src/__schemas__/null.ts +++ /dev/null @@ -1,86 +0,0 @@ -/** - * null_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: null, right: null): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { type: 'null', enum: [null] } } -export function toJsonSchema(): toJsonSchema { - function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } - return nullToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'null' } -export function toString(): 'null' { return 'null' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(nullSchema: null_): validate { - validateNull.tag = URI.null - function validateNull(u: unknown, path = Array.of()) { - return nullSchema(u) || [NullaryErrors.null(u, path)] - } - return validateNull -} -/// validate /// -////////////////////// - -export { null_ as null, null_ } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface null_ extends null_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function NullSchema(src: unknown): src is null { return src === null } -NullSchema.def = null -NullSchema.tag = URI.null - -const null_ = Object_assign( - NullSchema, - userDefinitions, -) as null_ - -Object_assign( - null_, - userExtensions, -) - -declare namespace null_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.null - _type: null - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/number.ts b/packages/schema/src/__schemas__/number.ts deleted file mode 100644 index f079373f..00000000 --- a/packages/schema/src/__schemas__/number.ts +++ /dev/null @@ -1,207 +0,0 @@ -/** - * number_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Bounds, - Equal, - Force, - PickIfDefined, - Unknown -} from '@traversable/registry' -import { - bindUserExtensions, - carryover, - Math_max, - Math_min, - Object_assign, - SameValueNumber, - URI, - within -} from '@traversable/registry' -import type { t } from '../_exports.js' -import type { NumericBounds } from '@traversable/schema-to-json-schema' -import { getNumericBounds } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: number, right: number): boolean { - return SameValueNumber(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: number_): toJsonSchema { - function numberToJsonSchema() { - const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) - let bounds: NumericBounds = {} - if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum - if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum - if (typeof minimum === 'number') bounds.minimum = minimum - if (typeof maximum === 'number') bounds.maximum = maximum - return { - type: 'number' as const, - ...bounds, - } - } - return numberToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'number' } -export function toString(): 'number' { return 'number' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(numberSchema: S): validate { - validateNumber.tag = URI.number - function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { - return numberSchema(u) || [NullaryErrors.number(u, path)] - } - return validateNumber -} -/// validate /// -////////////////////// - -export { number_ as number } - -interface number_ extends number_.core { - toString: toString - equals: equals - toJsonSchema: toJsonSchema - validate: validate -} - -export let userDefinitions: Record = { - toString, - equals, -} - -export let userExtensions: Record = { - toJsonSchema, - validate, -} - -function NumberSchema(src: unknown) { return typeof src === 'number' } -NumberSchema.tag = URI.number -NumberSchema.def = 0 - -const number_ = Object_assign( - NumberSchema, - userDefinitions, -) as number_ - -number_.min = function numberMin(minimum) { - return Object_assign( - boundedNumber({ gte: minimum }, carryover(this, 'minimum')), - { minimum }, - ) -} -number_.max = function numberMax(maximum) { - return Object_assign( - boundedNumber({ lte: maximum }, carryover(this, 'maximum')), - { maximum }, - ) -} -number_.moreThan = function numberMoreThan(exclusiveMinimum) { - return Object_assign( - boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), - { exclusiveMinimum }, - ) -} -number_.lessThan = function numberLessThan(exclusiveMaximum) { - return Object_assign( - boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), - { exclusiveMaximum }, - ) -} -number_.between = function numberBetween( - min, - max, - minimum = Math_min(min, max), - maximum = Math_max(min, max), -) { - return Object_assign( - boundedNumber({ gte: minimum, lte: maximum }), - { minimum, maximum }, - ) -} - -Object_assign( - number_, - bindUserExtensions(number_, userExtensions), -) - -function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ -function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ -function boundedNumber(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedNumberSchema(u: unknown) { - return typeof u === 'number' && within(bounds)(u) - }, carry, number_) -} - -declare namespace number_ { - interface core extends number_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: number - tag: URI.number - get def(): this['_type'] - minimum?: number - maximum?: number - exclusiveMinimum?: number - exclusiveMaximum?: number - } - interface methods { - min(minimum: Min): number_.Min - max(maximum: Max): number_.Max - moreThan(moreThan: Min): ExclusiveMin - lessThan(lessThan: Max): ExclusiveMax - between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ exclusiveMaximum: number }] - ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> - : [Self] extends [{ maximum: number }] - ? number_.between<[min: X, max: Self['maximum']]> - : number_.min - ; - type Max - = [Self] extends [{ exclusiveMinimum: number }] - ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> - : [Self] extends [{ minimum: number }] - ? number_.between<[min: Self['minimum'], max: X]> - : number_.max - ; - type ExclusiveMin - = [Self] extends [{ exclusiveMaximum: number }] - ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> - : [Self] extends [{ maximum: number }] - ? number_.maxStrictMin<[min: X, Self['maximum']]> - : number_.moreThan - ; - type ExclusiveMax - = [Self] extends [{ exclusiveMinimum: number }] - ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> - : [Self] extends [{ minimum: number }] - ? number_.minStrictMax<[Self['minimum'], min: X]> - : number_.lessThan - ; - interface min extends number_ { minimum: Min } - interface max extends number_ { maximum: Max } - interface moreThan extends number_ { exclusiveMinimum: Min } - interface lessThan extends number_ { exclusiveMaximum: Max } - interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } - interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } - interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } - interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } -} diff --git a/packages/schema/src/__schemas__/object.ts b/packages/schema/src/__schemas__/object.ts deleted file mode 100644 index 68b7ae4c..00000000 --- a/packages/schema/src/__schemas__/object.ts +++ /dev/null @@ -1,303 +0,0 @@ -/** - * object_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type * as T from '@traversable/registry' -import type { - Force, - Join, - Returns, - SchemaOptions as Options, - UnionToTuple, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - applyOptions, - Array_isArray, - bindUserExtensions, - fn, - has, - isAnyObject, - object as object$, - Object_assign, - Object_hasOwn, - Object_is, - Object_keys, - record as record$, - symbol, - typeName, - URI -} from '@traversable/registry' -import type { - Entry, - Optional, - Required, - Schema, - SchemaLike -} from '../_namespace.js' -import type { t } from '../_exports.js' -import { getConfig } from '../_exports.js' -import type { RequiredKeys } from '@traversable/schema-to-json-schema' -import { isRequired, property } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { Errors, NullaryErrors, UnaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | T.Equal -export function equals(objectSchema: object_): equals> -export function equals(objectSchema: object_): equals> -export function equals({ def }: object_<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { - function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { - if (Object_is(l, r)) return true - if (!l || typeof l !== 'object' || Array_isArray(l)) return false - if (!r || typeof r !== 'object' || Array_isArray(r)) return false - for (const k in def) { - const lHas = Object_hasOwn(l, k) - const rHas = Object_hasOwn(r, k) - if (lHas) { - if (!rHas) return false - if (!def[k].equals(l[k], r[k])) return false - } - if (rHas) { - if (!lHas) return false - if (!def[k].equals(l[k], r[k])) return false - } - if (!def[k].equals(l[k], r[k])) return false - } - return true - } - return objectEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema = RequiredKeys> { - (): { - type: 'object' - required: { [I in keyof KS]: KS[I] & string } - properties: { [K in keyof T]: Returns } - } -} - -export function toJsonSchema(objectSchema: object_): toJsonSchema -export function toJsonSchema(objectSchema: object_): toJsonSchema -export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { - const required = Object_keys(def).filter(isRequired(def)) - function objectToJsonSchema() { - return { - type: 'object' as const, - required, - properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), - } - } - return objectToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -/** @internal */ -type Symbol_optional = typeof Symbol_optional -const Symbol_optional: typeof symbol.optional = symbol.optional - -/** @internal */ -const hasOptionalSymbol = (u: unknown): u is { toString(): T } => - !!u && typeof u === 'function' - && Symbol_optional in u - && typeof u[Symbol_optional] === 'number' - -/** @internal */ -const hasToString = (x: unknown): x is { toString(): string } => - !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' - -export interface toString> { - (): never - | [keyof T] extends [never] ? '{}' - /* @ts-expect-error */ - : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` -} - -export function toString>(objectSchema: object_): toString -export function toString({ def }: object_) { - function objectToString() { - if (!!def && typeof def === 'object') { - const entries = Object.entries(def) - if (entries.length === 0) return '{}' - else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" - }: ${hasToString(x) ? x.toString() : 'unknown' - }`).join(', ') - } }` - } - else return '{ [x: string]: unknown }' - } - - return objectToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -/** @internal */ -let isObject = (u: unknown): u is { [x: string]: unknown } => - !!u && typeof u === 'object' && !Array_isArray(u) - -/** @internal */ -let isKeyOf = (k: keyof any, u: T): k is keyof T => - !!u && (typeof u === 'function' || typeof u === 'object') && k in u - -/** @internal */ -let isOptional = has('tag', (tag) => tag === URI.optional) - - -export type validate = never | ValidationFn - -export function validate(objectSchema: object_): validate -export function validate(objectSchema: object_): validate -export function validate(objectSchema: object_<{ [x: string]: Validator }>): validate<{ [x: string]: unknown }> { - validateObject.tag = URI.object - function validateObject(u: unknown, path_ = Array.of()) { - // if (objectSchema(u)) return true - if (!isObject(u)) return [Errors.object(u, path_)] - let errors = Array.of() - let { schema: { optionalTreatment } } = getConfig() - let keys = Object_keys(objectSchema.def) - if (optionalTreatment === 'exactOptional') { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path = [...path_, k] - if (Object_hasOwn(u, k) && u[k] === undefined) { - if (isOptional(objectSchema.def[k].validate)) { - let tag = typeName(objectSchema.def[k].validate) - if (isKeyOf(tag, NullaryErrors)) { - let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] - errors.push(NullaryErrors[tag](...args)) - } - else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) - } - } - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - let tag = typeName(objectSchema.def[k].validate) - if (isKeyOf(tag, NullaryErrors)) { - errors.push(NullaryErrors[tag](u[k], path, tag)) - } - else if (isKeyOf(tag, UnaryErrors)) { - errors.push(UnaryErrors[tag].invalid(u[k], path)) - } - errors.push(...results) - } - else if (Object_hasOwn(u, k)) { - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - errors.push(...results) - continue - } else { - errors.push(UnaryErrors.object.missing(u, path)) - continue - } - } - } - else { - for (let i = 0, len = keys.length; i < len; i++) { - let k = keys[i] - let path = [...path_, k] - if (!Object_hasOwn(u, k)) { - if (!isOptional(objectSchema.def[k].validate)) { - errors.push(UnaryErrors.object.missing(u, path)) - continue - } - else { - if (!Object_hasOwn(u, k)) continue - if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { - if (u[k] === undefined) continue - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - for (let j = 0; j < results.length; j++) { - let result = results[j] - errors.push(result) - continue - } - } - } - } - let results = objectSchema.def[k].validate(u[k], path) - if (results === true) continue - for (let l = 0; l < results.length; l++) { - let result = results[l] - errors.push(result) - } - } - } - return errors.length === 0 || errors - } - - return validateObject -} -/// validate /// -////////////////////// - -export { object_ as object } - -function object_< - S extends { [x: string]: Schema }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_< - S extends { [x: string]: SchemaLike }, - T extends { [K in keyof S]: Entry } ->(schemas: S, options?: Options): object_ -function object_(schemas: { [x: string]: Schema }, options?: Options) { - return object_.def(schemas, options) -} - -interface object_ extends object_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -namespace object_ { - export let userDefinitions: Record = { - } as object_ - export function def(xs: T, $?: Options, opt?: string[]): object_ - export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const keys = Object_keys(xs) - const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) - const req = keys.filter((k) => !has(symbol.optional)(xs[k])) - const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) - function ObjectSchema(src: unknown) { return predicate(src) } - ObjectSchema.tag = URI.object - ObjectSchema.def = xs - ObjectSchema.opt = opt - ObjectSchema.req = req - Object_assign(ObjectSchema, userDefinitions) - return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) - } -} - -declare namespace object_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: object_.type - tag: URI.object - get def(): S - opt: Optional // TODO: use object_.Opt? - req: Required // TODO: use object_.Req? - } - type Opt = symbol.optional extends keyof S[K] ? never : K - type Req = symbol.optional extends keyof S[K] ? K : never - type type = Force< - & { [K in keyof S as Opt]-?: S[K]['_type' & keyof S[K]] } - & { [K in keyof S as Req]+?: S[K]['_type' & keyof S[K]] } - > -} diff --git a/packages/schema/src/__schemas__/of.ts b/packages/schema/src/__schemas__/of.ts deleted file mode 100644 index 94bae7ec..00000000 --- a/packages/schema/src/__schemas__/of.ts +++ /dev/null @@ -1,94 +0,0 @@ -/** - * of schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Unknown } from '@traversable/registry' -import { Equal, Object_assign, URI } from '@traversable/registry' -import type { - Entry, - Guard, - Guarded, - SchemaLike -} from '../_namespace.js' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: T, right: T): boolean { - return Equal.lax(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function inlineToJsonSchema(): void { - return void 0 - } - return inlineToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'unknown' } -export function toString(): 'unknown' { return 'unknown' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(inlineSchema: of): validate { - validateInline.tag = URI.inline - function validateInline(u: unknown, path = Array.of()) { - return inlineSchema(u) || [NullaryErrors.inline(u, path)] - } - return validateInline -} -/// validate /// -////////////////////// - -export interface of extends of.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export function of(typeguard: S): Entry -export function of(typeguard: S): of -export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { - typeguard.def = typeguard - return Object_assign(typeguard, of.prototype) -} - -export namespace of { - export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, - } - export let userExtensions: Record = { - validate, - } - export function def(guard: T): of - export function def(guard: T) { - function InlineSchema(src: unknown) { return guard(src) } - InlineSchema.tag = URI.inline - InlineSchema.def = guard - return InlineSchema - } -} - -export declare namespace of { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - _type: Guarded - tag: URI.inline - get def(): S - } - type type> = never | T -} diff --git a/packages/schema/src/__schemas__/optional.ts b/packages/schema/src/__schemas__/optional.ts deleted file mode 100644 index 426e5843..00000000 --- a/packages/schema/src/__schemas__/optional.ts +++ /dev/null @@ -1,135 +0,0 @@ -/** - * optional schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Equal, - Force, - Returns, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - bindUserExtensions, - has, - isUnknown as isAny, - Object_assign, - Object_is, - optional as optional$, - symbol, - URI -} from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../_exports.js' -import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' -import { callToString } from '@traversable/schema-to-string' -import type { Validate, ValidationFn, Validator } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | Equal -export function equals(optionalSchema: optional): equals -export function equals(optionalSchema: optional): equals -export function equals({ def }: optional<{ equals: Equal }>): Equal { - return function optionalEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - return def.equals(l, r) - } -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -type Nullable = Force - -export interface toJsonSchema { - (): Nullable> - [symbol.optional]: number -} - -export function toJsonSchema(optionalSchema: optional): toJsonSchema -export function toJsonSchema({ def }: optional) { - function optionalToJsonSchema() { return getSchema(def) } - optionalToJsonSchema[symbol.optional] = wrapOptional(def) - return optionalToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - /* @ts-expect-error */ - (): never | `(${ReturnType} | undefined)` -} - -export function toString(optionalSchema: optional): toString -export function toString({ def }: optional): () => string { - function optionalToString(): string { - return '(' + callToString(def) + ' | undefined)' - } - return optionalToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate - -export function validate(optionalSchema: optional): validate -export function validate(optionalSchema: optional): validate -export function validate({ def }: optional): ValidationFn { - validateOptional.tag = URI.optional - validateOptional.optional = 1 - function validateOptional(u: unknown, path = Array.of()) { - if (u === void 0) return true - return def.validate(u, path) - } - return validateOptional -} -/// validate /// -////////////////////// - -export function optional(schema: S): optional -export function optional(schema: S): optional> -export function optional(schema: S): optional { return optional.def(schema) } - -export interface optional extends optional.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export namespace optional { - export let userDefinitions: Record = { - } - export function def(x: T): optional - export function def(x: T) { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const predicate = _isPredicate(x) ? optional$(x) : isAny - function OptionalSchema(src: unknown) { return predicate(src) } - OptionalSchema.tag = URI.optional - OptionalSchema.def = x - OptionalSchema[symbol.optional] = 1 - Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) - return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) - } - export const is - : (u: unknown) => u is optional - = has('tag', (u) => u === URI.optional) -} - -export declare namespace optional { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.optional - _type: undefined | S['_type' & keyof S] - def: S - [symbol.optional]: number - } - export type type = never | T -} diff --git a/packages/schema/src/__schemas__/record.ts b/packages/schema/src/__schemas__/record.ts deleted file mode 100644 index 63d12e6c..00000000 --- a/packages/schema/src/__schemas__/record.ts +++ /dev/null @@ -1,160 +0,0 @@ -/** - * record schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type * as T from '@traversable/registry' -import type { Equal, Returns, Unknown } from '@traversable/registry' -import { - _isPredicate, - Array_isArray, - bindUserExtensions, - isAnyObject, - Object_assign, - Object_hasOwn, - Object_is, - Object_keys, - record as record$, - URI -} from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../_exports.js' -import { getSchema } from '@traversable/schema-to-json-schema' -import { callToString } from '@traversable/schema-to-string' -import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = never | Equal -export function equals(recordSchema: record): equals -export function equals(recordSchema: record): equals -export function equals({ def }: record<{ equals: Equal }>): Equal> { - function recordEquals(l: Record, r: Record): boolean { - if (Object_is(l, r)) return true - if (!l || typeof l !== 'object' || Array_isArray(l)) return false - if (!r || typeof r !== 'object' || Array_isArray(r)) return false - const lhs = Object_keys(l) - const rhs = Object_keys(r) - let len = lhs.length - let k: string - if (len !== rhs.length) return false - for (let ix = len; ix-- !== 0;) { - k = lhs[ix] - if (!Object_hasOwn(r, k)) return false - if (!(def.equals(l[k], r[k]))) return false - } - len = rhs.length - for (let ix = len; ix-- !== 0;) { - k = rhs[ix] - if (!Object_hasOwn(l, k)) return false - if (!(def.equals(l[k], r[k]))) return false - } - return true - } - return recordEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): { - type: 'object' - additionalProperties: T.Returns - } -} - -export function toJsonSchema(recordSchema: record): toJsonSchema -export function toJsonSchema(recordSchema: record): toJsonSchema -export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { - return function recordToJsonSchema() { - return { - type: 'object' as const, - additionalProperties: getSchema(def), - } - } -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - /* @ts-expect-error */ - (): never | `Record}>` -} - -export function toString>(recordSchema: S): toString -export function toString(recordSchema: record): toString -export function toString({ def }: { def: unknown }): () => string { - function recordToString() { - return `Record` - } - return recordToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = never | ValidationFn -export function validate(recordSchema: record): validate -export function validate(recordSchema: record): validate -export function validate({ def: { validate = () => true } }: record) { - validateRecord.tag = URI.record - function validateRecord(u: unknown, path = Array.of()) { - if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] - let errors = Array.of() - let keys = Object_keys(u) - for (let k of keys) { - let y = u[k] - let results = validate(y, [...path, k]) - if (results === true) continue - else errors.push(...results) - } - return errors.length === 0 || errors - } - return validateRecord -} -/// validate /// -////////////////////// - -export function record(schema: S): record -export function record(schema: S): record> -export function record(schema: Schema) { - return record.def(schema) -} - -export interface record extends record.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export namespace record { - export let userDefinitions: Record = { - } - export function def(x: T): record - export function def(x: unknown): {} { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const predicate = _isPredicate(x) ? record$(x) : isAnyObject - function RecordSchema(src: unknown) { return predicate(src) } - RecordSchema.tag = URI.record - RecordSchema.def = x - Object_assign(RecordSchema, record.userDefinitions) - return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) - } -} - -export declare namespace record { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.record - get def(): S - _type: Record - } - export type type> = never | T -} diff --git a/packages/schema/src/__schemas__/string.ts b/packages/schema/src/__schemas__/string.ts deleted file mode 100644 index deb1dd42..00000000 --- a/packages/schema/src/__schemas__/string.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * string_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Bounds, - Equal, - Force, - Integer, - PickIfDefined, - Unknown -} from '@traversable/registry' -import { - bindUserExtensions, - carryover, - has, - Math_max, - Math_min, - Object_assign, - URI, - within -} from '@traversable/registry' -import type { t } from '../_exports.js' -import type { SizeBounds } from '@traversable/schema-to-json-schema' -import type { ValidationError, ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: string, right: string): boolean { - return left === right -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): Force<{ type: 'string' } & PickIfDefined> -} - -export function toJsonSchema(schema: S): toJsonSchema -export function toJsonSchema(schema: string_): () => { type: 'string' } & Partial { - function stringToJsonSchema() { - const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null - const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null - let out: { type: 'string' } & Partial = { type: 'string' } - minLength !== null && void (out.minLength = minLength) - maxLength !== null && void (out.maxLength = maxLength) - - return out - } - return stringToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'string' } -export function toString(): 'string' { return 'string' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(stringSchema: S): validate { - validateString.tag = URI.string - function validateString(u: unknown, path = Array.of()): true | ValidationError[] { - return stringSchema(u) || [NullaryErrors.number(u, path)] - } - return validateString -} -/// validate /// -////////////////////// - -export { string_ as string } - -/** @internal */ -function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ -function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ -function boundedString(bounds: Bounds, carry?: {}): {} { - return Object_assign(function BoundedStringSchema(u: unknown) { - return string_(u) && within(bounds)(u.length) - }, carry, string_) -} - -interface string_ extends string_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export let userDefinitions: Record = { - toString, - equals, -} - -export let userExtensions: Record = { - toJsonSchema, - validate, -} - -function StringSchema(src: unknown) { return typeof src === 'string' } -StringSchema.tag = URI.string -StringSchema.def = '' - -const string_ = Object_assign( - StringSchema, - userDefinitions, -) as string_ - -string_.min = function stringMinLength(minLength) { - return Object_assign( - boundedString({ gte: minLength }, carryover(this, 'minLength')), - { minLength }, - ) -} -string_.max = function stringMaxLength(maxLength) { - return Object_assign( - boundedString({ lte: maxLength }, carryover(this, 'maxLength')), - { maxLength }, - ) -} -string_.between = function stringBetween( - min, - max, - minLength = Math_min(min, max), - maxLength = Math_max(min, max)) { - return Object_assign( - boundedString({ gte: minLength, lte: maxLength }), - { minLength, maxLength }, - ) -} - -Object_assign( - string_, - bindUserExtensions(string_, userExtensions), -) - -declare namespace string_ { - interface core extends string_.methods { - (u: this['_type'] | Unknown): u is this['_type'] - _type: string - tag: URI.string - get def(): this['_type'] - } - interface methods { - minLength?: number - maxLength?: number - min>(minLength: Min): string_.Min - max>(maxLength: Max): string_.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> - } - type Min - = [Self] extends [{ maxLength: number }] - ? string_.between<[min: Min, max: Self['maxLength']]> - : string_.min - ; - type Max - = [Self] extends [{ minLength: number }] - ? string_.between<[min: Self['minLength'], max: Max]> - : string_.max - ; - interface min extends string_ { minLength: Min } - interface max extends string_ { maxLength: Max } - interface between extends string_ { - minLength: Bounds[0] - maxLength: Bounds[1] - } -} diff --git a/packages/schema/src/__schemas__/symbol.ts b/packages/schema/src/__schemas__/symbol.ts deleted file mode 100644 index 201b1ec0..00000000 --- a/packages/schema/src/__schemas__/symbol.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * symbol_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: symbol, right: symbol): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function symbolToJsonSchema() { return void 0 } - return symbolToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'symbol' } -export function toString(): 'symbol' { return 'symbol' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(symbolSchema: symbol_): validate { - validateSymbol.tag = URI.symbol - function validateSymbol(u: unknown, path = Array.of()) { - return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] - } - return validateSymbol -} -/// validate /// -////////////////////// - -export { symbol_ as symbol } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface symbol_ extends symbol_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } -SymbolSchema.tag = URI.symbol -SymbolSchema.def = Symbol() - -const symbol_ = Object_assign( - SymbolSchema, - userDefinitions, -) as symbol_ - -Object_assign(symbol_, userExtensions) - -declare namespace symbol_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.symbol - _type: symbol - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/tuple.ts b/packages/schema/src/__schemas__/tuple.ts deleted file mode 100644 index eb0603bc..00000000 --- a/packages/schema/src/__schemas__/tuple.ts +++ /dev/null @@ -1,225 +0,0 @@ -/** - * tuple schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Equal, - Join, - Returns, - SchemaOptions as Options, - TypeError, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - Array_isArray, - bindUserExtensions, - getConfig, - has, - Object_assign, - Object_hasOwn, - Object_is, - parseArgs, - symbol, - tuple as tuple$, - URI -} from '@traversable/registry' -import type { - Entry, - FirstOptionalItem, - invalid, - Schema, - SchemaLike, - TupleType, - ValidateTuple -} from '../_namespace.js' -import type { optional } from './optional.js' -import type { t } from '../_exports.js' -import type { MinItems } from '@traversable/schema-to-json-schema' -import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' -import { hasToString } from '@traversable/schema-to-string' -import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' -import { Errors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal - -export function equals(tupleSchema: tuple): equals -export function equals(tupleSchema: tuple): equals -export function equals(tupleSchema: tuple) { - function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { - if (Object_is(l, r)) return true - if (Array_isArray(l)) { - if (!Array_isArray(r)) return false - for (let ix = tupleSchema.def.length; ix-- !== 0;) { - if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue - if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false - if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false - if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { - if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false - } - } - return true - } - return false - } - return tupleEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): { - type: 'array', - items: { [I in keyof T]: Returns } - additionalItems: false - minItems: MinItems - maxItems: T['length' & keyof T] - } -} - -export function toJsonSchema(tupleSchema: tuple): toJsonSchema -export function toJsonSchema({ def }: tuple): () => { - type: 'array' - items: unknown - additionalItems: false - minItems?: {} - maxItems?: number -} { - function tupleToJsonSchema() { - let min = minItems(def) - let max = def.length - let items = applyTupleOptionality(def, { min, max }) - return { - type: 'array' as const, - additionalItems: false as const, - items, - minItems: min, - maxItems: max, - } - } - return tupleToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - (): never | `[${Join<{ - [I in keyof T]: `${ - /* @ts-expect-error */ - T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType - }` - }, ', '>}]` -} - -export function toString(tupleSchema: tuple): toString -export function toString(tupleSchema: tuple): () => string { - let isOptional = has('tag', (tag) => tag === URI.optional) - function stringToString() { - return Array_isArray(tupleSchema.def) - ? `[${tupleSchema.def.map( - (x) => isOptional(x) - ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` - : hasToString(x) ? x.toString() : 'unknown' - ).join(', ')}]` : 'unknown[]' - } - return stringToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate -export function validate(tupleSchema: tuple<[...S]>): validate -export function validate(tupleSchema: tuple<[...S]>): validate -export function validate(tupleSchema: tuple<[...S]>): Validate { - validateTuple.tag = URI.tuple - let isOptional = has('tag', (tag) => tag === URI.optional) - function validateTuple(u: unknown, path = Array.of()) { - let errors = Array.of() - if (!Array_isArray(u)) return [Errors.array(u, path)] - for (let i = 0; i < tupleSchema.def.length; i++) { - if (!(i in u) && !(isOptional(tupleSchema.def[i].validate))) { - errors.push(Errors.missingIndex(u, [...path, i])) - continue - } - let results = tupleSchema.def[i].validate(u[i], [...path, i]) - if (results !== true) { - for (let j = 0; j < results.length; j++) errors.push(results[j]) - results.push(Errors.arrayElement(u[i], [...path, i])) - } - } - if (u.length > tupleSchema.def.length) { - for (let k = tupleSchema.def.length; k < u.length; k++) { - let excess = u[k] - errors.push(Errors.excessItems(excess, [...path, k])) - } - } - return errors.length === 0 || errors - } - return validateTuple -} -/// validate /// -////////////////////// - -export { tuple } - -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> -function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> -function tuple }>(...schemas: tuple.validate): tuple, T>> -function tuple(...schemas: tuple.validate): tuple, S>> -function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { - return tuple.def(...parseArgs(getConfig().schema, args)) -} - -interface tuple extends tuple.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -namespace tuple { - export let userDefinitions: Record = { - } as tuple - export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple - export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const opt = opt_ || xs.findIndex(has(symbol.optional)) - const options = { - ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) - } satisfies tuple.InternalOptions - const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) - function TupleSchema(src: unknown) { return predicate(src) } - TupleSchema.tag = URI.tuple - TupleSchema.def = xs - TupleSchema.opt = opt - Object_assign(TupleSchema, tuple.userDefinitions) - return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) - } -} - -declare namespace tuple { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.tuple - _type: TupleType - opt: FirstOptionalItem - def: S - } - type type> = never | T - type InternalOptions = { minLength?: number } - type validate = ValidateTuple> - - type from - = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T -} diff --git a/packages/schema/src/__schemas__/undefined.ts b/packages/schema/src/__schemas__/undefined.ts deleted file mode 100644 index dfbc9410..00000000 --- a/packages/schema/src/__schemas__/undefined.ts +++ /dev/null @@ -1,83 +0,0 @@ -/** - * undefined_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: undefined, right: undefined): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function undefinedToJsonSchema(): void { return void 0 } - return undefinedToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'undefined' } -export function toString(): 'undefined' { return 'undefined' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(undefinedSchema: undefined_): validate { - validateUndefined.tag = URI.undefined - function validateUndefined(u: unknown, path = Array.of()) { - return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] - } - return validateUndefined -} -/// validate /// -////////////////////// - -export { undefined_ as undefined } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface undefined_ extends undefined_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } -UndefinedSchema.tag = URI.undefined -UndefinedSchema.def = void 0 as undefined - -const undefined_ = Object_assign( - UndefinedSchema, - userDefinitions, -) as undefined_ - -Object_assign(undefined_, userExtensions) - -declare namespace undefined_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.undefined - _type: undefined - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/union.ts b/packages/schema/src/__schemas__/union.ts deleted file mode 100644 index 0bf23565..00000000 --- a/packages/schema/src/__schemas__/union.ts +++ /dev/null @@ -1,144 +0,0 @@ -/** - * union schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { - Equal, - Join, - Returns, - Unknown -} from '@traversable/registry' -import { - _isPredicate, - Array_isArray, - bindUserExtensions, - isUnknown as isAny, - Object_assign, - Object_is, - union as union$, - URI -} from '@traversable/registry' -import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../_exports.js' -import { getSchema } from '@traversable/schema-to-json-schema' -import { callToString } from '@traversable/schema-to-string' -import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(unionSchema: union<[...S]>): equals -export function equals(unionSchema: union<[...S]>): equals -export function equals({ def }: union<{ equals: Equal }[]>): Equal { - function unionEquals(l: unknown, r: unknown): boolean { - if (Object_is(l, r)) return true - for (let ix = def.length; ix-- !== 0;) - if (def[ix].equals(l, r)) return true - return false - } - return unionEquals -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { - (): { anyOf: { [I in keyof T]: Returns } } -} - -export function toJsonSchema(unionSchema: union): toJsonSchema -export function toJsonSchema(unionSchema: union): toJsonSchema -export function toJsonSchema({ def }: union): () => {} { - return function unionToJsonSchema() { - return { - anyOf: def.map(getSchema) - } - } -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { - (): never | [T] extends [readonly []] ? 'never' - /* @ts-expect-error */ - : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` -} - -export function toString(unionSchema: union): toString -export function toString({ def }: union): () => string { - function unionToString() { - return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' - } - return unionToString -} -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = Validate - -export function validate(unionSchema: union): validate -export function validate(unionSchema: union): validate -export function validate({ def }: union) { - validateUnion.tag = URI.union - function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { - // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; - let errors = Array.of() - for (let i = 0; i < def.length; i++) { - let results = def[i].validate(u, path) - if (results === true) { - // validateUnion.optional = 0 - return true - } - for (let j = 0; j < results.length; j++) errors.push(results[j]) - } - // validateUnion.optional = 0 - return errors.length === 0 || errors - } - return validateUnion -} -/// validate /// -////////////////////// - -export function union(...schemas: S): union -export function union }>(...schemas: S): union -export function union(...schemas: unknown[]) { - return union.def(schemas) -} - -export interface union extends union.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -export namespace union { - export let userDefinitions: Record = { - } as Partial> - export function def(xs: T): union - export function def(xs: unknown[]) { - let userExtensions: Record = { - toString, - equals, - toJsonSchema, - validate, - } - const predicate = xs.every(_isPredicate) ? union$(xs) : isAny - function UnionSchema(src: unknown): src is unknown { return predicate(src) } - UnionSchema.tag = URI.union - UnionSchema.def = xs - Object_assign(UnionSchema, union.userDefinitions) - return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) - } -} - -export declare namespace union { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.union - _type: union.type - get def(): S - } - type type = never | T -} diff --git a/packages/schema/src/__schemas__/unknown.ts b/packages/schema/src/__schemas__/unknown.ts deleted file mode 100644 index caaf06c6..00000000 --- a/packages/schema/src/__schemas__/unknown.ts +++ /dev/null @@ -1,80 +0,0 @@ -/** - * unknown_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: any, right: any): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } -export function toJsonSchema(): toJsonSchema { - function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } - return anyToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'unknown' } -export function toString(): 'unknown' { return 'unknown' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(_?: unknown_): validate { - validateUnknown.tag = URI.unknown - function validateUnknown() { return true as const } - return validateUnknown -} -/// validate /// -////////////////////// - -export { unknown_ as unknown } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface unknown_ extends unknown_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function UnknownSchema(src: unknown): src is unknown { return true } -UnknownSchema.tag = URI.unknown -UnknownSchema.def = void 0 as unknown - -const unknown_ = Object_assign( - UnknownSchema, - userDefinitions, -) as unknown_ - -Object_assign(unknown_, userExtensions) - -declare namespace unknown_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.unknown - _type: unknown - get def(): this['_type'] - } -} diff --git a/packages/schema/src/__schemas__/void.ts b/packages/schema/src/__schemas__/void.ts deleted file mode 100644 index 41bd95b3..00000000 --- a/packages/schema/src/__schemas__/void.ts +++ /dev/null @@ -1,85 +0,0 @@ -/** - * void_ schema - * made with ᯓᡣ𐭩 by @traversable/schema - */ -import type { Equal, Unknown } from '@traversable/registry' -import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../_exports.js' -import type { ValidationFn } from '@traversable/derive-validators' -import { NullaryErrors } from '@traversable/derive-validators' - //////////////////// -/// equals /// -export type equals = Equal -export function equals(left: void, right: void): boolean { - return Object_is(left, right) -} -/// equals /// -//////////////////// - ////////////////////////// -/// toJsonSchema /// -export interface toJsonSchema { (): void } -export function toJsonSchema(): toJsonSchema { - function voidToJsonSchema(): void { - return void 0 - } - return voidToJsonSchema -} -/// toJsonSchema /// -////////////////////////// - ////////////////////// -/// toString /// -export interface toString { (): 'void' } -export function toString(): 'void' { return 'void' } -/// toString /// -////////////////////// - ////////////////////// -/// validate /// -export type validate = ValidationFn -export function validate(voidSchema: void_): validate { - validateVoid.tag = URI.void - function validateVoid(u: unknown, path = Array.of()) { - return voidSchema(u) || [NullaryErrors.void(u, path)] - } - return validateVoid -} -/// validate /// -////////////////////// - -export { void_ as void, void_ } - -export let userDefinitions: Record = { - equals, - toJsonSchema, - toString, -} - -export let userExtensions: Record = { - validate, -} - -interface void_ extends void_.core { - equals: equals - toJsonSchema: toJsonSchema - toString: toString - validate: validate -} - -function VoidSchema(src: unknown): src is void { return src === void 0 } -VoidSchema.tag = URI.void -VoidSchema.def = void 0 as void - -const void_ = Object_assign( - VoidSchema, - userDefinitions, -) as void_ - -Object_assign(void_, userExtensions) - -declare namespace void_ { - interface core { - (u: this['_type'] | Unknown): u is this['_type'] - tag: URI.void - _type: void - get def(): this['_type'] - } -} From 2642e9b2e0278d7278283345daf6c84be9276bdd Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 16 Apr 2025 14:52:10 -0500 Subject: [PATCH 36/45] feat(jit): jitless with formatting --- README.md | 2 + bin/workspace.ts | 9 +- config/__generated__/package-list.ts | 1 + packages/json/src/exports.ts | 1 + packages/registry/src/exports.ts | 2 +- packages/registry/src/parse.ts | 53 +- packages/registry/src/pick.ts | 1 + packages/registry/src/types.ts | 42 +- packages/schema-core/src/core.ts | 74 +- packages/schema-core/src/exports.ts | 8 + packages/schema-core/src/namespace.ts | 3 + packages/schema-core/src/schemas/bigint.ts | 13 +- packages/schema-core/src/schemas/integer.ts | 9 +- packages/schema-core/src/schemas/number.ts | 21 +- packages/schema-core/src/schemas/object.ts | 1 + packages/schema-core/src/schemas/string.ts | 13 +- packages/schema-jit-compiler/README.md | 64 + packages/schema-jit-compiler/package.json | 47 + .../src/__generated__/__manifest__.ts | 46 + packages/schema-jit-compiler/src/exports.ts | 30 + packages/schema-jit-compiler/src/functor.ts | 211 ++ packages/schema-jit-compiler/src/index.ts | 1 + packages/schema-jit-compiler/src/jit.ts | 362 +++ packages/schema-jit-compiler/src/json.ts | 143 ++ packages/schema-jit-compiler/src/sort.ts | 212 ++ packages/schema-jit-compiler/src/version.ts | 3 + packages/schema-jit-compiler/test/TODO.ts | 106 + .../schema-jit-compiler/test/functor.test.ts | 9 + packages/schema-jit-compiler/test/jit.test.ts | 1987 +++++++++++++++++ .../schema-jit-compiler/test/json.test.ts | 167 ++ .../schema-jit-compiler/test/sort.test.ts | 61 + .../schema-jit-compiler/test/version.test.ts | 10 + .../schema-jit-compiler/tsconfig.build.json | 15 + packages/schema-jit-compiler/tsconfig.json | 8 + .../schema-jit-compiler/tsconfig.src.json | 15 + .../schema-jit-compiler/tsconfig.test.json | 16 + packages/schema-jit-compiler/vite.config.ts | 6 + pnpm-lock.yaml | 13 + tsconfig.base.json | 6 + tsconfig.build.json | 1 + tsconfig.json | 1 + 41 files changed, 3733 insertions(+), 60 deletions(-) create mode 100644 packages/schema-jit-compiler/README.md create mode 100644 packages/schema-jit-compiler/package.json create mode 100644 packages/schema-jit-compiler/src/__generated__/__manifest__.ts create mode 100644 packages/schema-jit-compiler/src/exports.ts create mode 100644 packages/schema-jit-compiler/src/functor.ts create mode 100644 packages/schema-jit-compiler/src/index.ts create mode 100644 packages/schema-jit-compiler/src/jit.ts create mode 100644 packages/schema-jit-compiler/src/json.ts create mode 100644 packages/schema-jit-compiler/src/sort.ts create mode 100644 packages/schema-jit-compiler/src/version.ts create mode 100644 packages/schema-jit-compiler/test/TODO.ts create mode 100644 packages/schema-jit-compiler/test/functor.test.ts create mode 100644 packages/schema-jit-compiler/test/jit.test.ts create mode 100644 packages/schema-jit-compiler/test/json.test.ts create mode 100644 packages/schema-jit-compiler/test/sort.test.ts create mode 100644 packages/schema-jit-compiler/test/version.test.ts create mode 100644 packages/schema-jit-compiler/tsconfig.build.json create mode 100644 packages/schema-jit-compiler/tsconfig.json create mode 100644 packages/schema-jit-compiler/tsconfig.src.json create mode 100644 packages/schema-jit-compiler/tsconfig.test.json create mode 100644 packages/schema-jit-compiler/vite.config.ts diff --git a/README.md b/README.md index 8154c30a..3b63eb15 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,8 @@ flowchart TD derive-validators(derive-validators) -.-> json(json) derive-validators(derive-validators) -.-> registry(registry) derive-validators(derive-validators) -.-> schema-core(schema-core) + schema-jit-compiler(schema-jit-compiler) -.-> registry(registry) + schema-jit-compiler(schema-jit-compiler) -.-> schema-core(schema-core) schema-seed(schema-seed) -.-> json(json) schema-seed(schema-seed) -.-> registry(registry) schema-seed(schema-seed) -.-> schema-core(schema-core) diff --git a/bin/workspace.ts b/bin/workspace.ts index c0962076..d668b17c 100755 --- a/bin/workspace.ts +++ b/bin/workspace.ts @@ -430,7 +430,6 @@ namespace write { ($) => pipe( [ `export * from './exports.js'`, - `export * as ${Transform.toCamelCase($.pkgName)} from './exports.js'`, ].join('\n'), $.dryRun ? tap(`\n\n[CREATE #10]: workspaceIndex\n`, globalThis.String) : fs.writeString(path.join(PATH.packages, $.pkgName, 'src', 'index.ts')), @@ -506,6 +505,7 @@ namespace write { `import pkg from './__generated__/__manifest__.js'`, `export const VERSION = \`\${pkg.name}@\${pkg.version}\` as const`, `export type VERSION = typeof VERSION`, + ``, ].join('\n'), $.dryRun ? tap(`\n\n[CREATE #14]: workspaceVersionSrc\n`, globalThis.String) : fs.writeString(path.join(PATH.packages, $.pkgName, 'src', 'version.ts')), @@ -521,14 +521,15 @@ namespace write { ([ `import * as vi from 'vitest'`, `import pkg from '../package.json' with { type: 'json' }`, - `import { ${Transform.toCamelCase($.pkgName)} } from '${SCOPE}/${$.pkgName}'`, + `import { VERSION } from '${SCOPE}/${$.pkgName}'`, ``, `vi.describe('〖⛳️〗‹‹‹ ❲${SCOPE}/${$.pkgName}❳', () => {`, - ` vi.it('〖⛳️〗› ❲${Transform.toCamelCase($.pkgName)}#VERSION❳', () => {`, + ` vi.it('〖⛳️〗› ❲VERSION❳', () => {`, ` const expected = \`\${pkg.name}@\${pkg.version}\``, - ` vi.assert.equal(${Transform.toCamelCase($.pkgName)}.VERSION, expected)`, + ` vi.assert.equal(VERSION, expected)`, ` })`, `})`, + ``, ]).join('\n'), $.dryRun ? tap(`\n\n[CREATE #15]: workspaceVersionTest\n`, globalThis.String) : fs.writeString(path.join(PATH.packages, $.pkgName, 'test', 'version.test.ts')), diff --git a/config/__generated__/package-list.ts b/config/__generated__/package-list.ts index 160fc59c..8862b0df 100644 --- a/config/__generated__/package-list.ts +++ b/config/__generated__/package-list.ts @@ -7,6 +7,7 @@ export const PACKAGES = [ "packages/schema", "packages/schema-core", "packages/schema-generator", + "packages/schema-jit-compiler", "packages/schema-seed", "packages/schema-to-json-schema", "packages/schema-to-string", diff --git a/packages/json/src/exports.ts b/packages/json/src/exports.ts index 3c58e482..7b041c78 100644 --- a/packages/json/src/exports.ts +++ b/packages/json/src/exports.ts @@ -5,6 +5,7 @@ export { VERSION } from './version.js' export { Cache } from './cache.js' export * as Json from './json.js' +export type { Free, Unary } from './json.js' /** * ## {@link Json `Json`} diff --git a/packages/registry/src/exports.ts b/packages/registry/src/exports.ts index ef0a572a..187567aa 100644 --- a/packages/registry/src/exports.ts +++ b/packages/registry/src/exports.ts @@ -30,7 +30,7 @@ export { carryover, within, withinBig } from './bounded.js' export { join } from './join.js' export { has } from './has.js' export { parseArgs } from './parseArgs.js' -export { escape, isQuoted, isValidIdentifier, parseKey } from './parse.js' +export { ESC_CHAR, PATTERN, escape, isQuoted, isValidIdentifier, parseKey } from './parse.js' export { IsStrictlyEqual, SameType, SameValue, SameValueNumber, deep as deepEquals, lax as laxEquals } from './equals.js' export { unsafeCompact } from './compact.js' export { bindUserExtensions } from './bindUserExtensions.js' diff --git a/packages/registry/src/parse.ts b/packages/registry/src/parse.ts index d599500a..464f6094 100644 --- a/packages/registry/src/parse.ts +++ b/packages/registry/src/parse.ts @@ -1,14 +1,14 @@ -const PATTERN = { +export let PATTERN = { singleQuoted: /(?<=^').+?(?='$)/, doubleQuoted: /(?<=^").+?(?="$)/, graveQuoted: /(?<=^`).+?(?=`$)/, identifier: /^[$_\p{ID_Start}][$\u200c\u200d\p{ID_Continue}]*$/u, } as const satisfies Record -export const isQuoted +export let isQuoted : (text: string | number) => boolean = (text) => { - const string = `${text}` + let string = `${text}` return ( PATTERN.singleQuoted.test(string) || PATTERN.doubleQuoted.test(string) || @@ -16,13 +16,12 @@ export const isQuoted ) } -export const isValidIdentifier = (name: keyof any): boolean => +export let isValidIdentifier = (name: keyof any): boolean => typeof name === "symbol" ? true : isQuoted(name) || PATTERN.identifier.test(`${name}`) - -const ESC_CHAR = [ - /** 0- 9 */ '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004', '\\u0005', '\\u0006', '\\u0007', '\\b', '\\t', +export let ESC_CHAR = [ + /** 00-09 */ '\\u0000', '\\u0001', '\\u0002', '\\u0003', '\\u0004', '\\u0005', '\\u0006', '\\u0007', '\\b', '\\t', /** 10-19 */ '\\n', '\\u000b', '\\f', '\\r', '\\u000e', '\\u000f', '\\u0010', '\\u0011', '\\u0012', '\\u0013', /** 20-29 */ '\\u0014', '\\u0015', '\\u0016', '\\u0017', '\\u0018', '\\u0019', '\\u001a', '\\u001b', '\\u001c', '\\u001d', /** 30-39 */ '\\u001e', '\\u001f', '', '', '\\"', '', '', '', '', '', @@ -67,7 +66,7 @@ export function escape(x: string): string { let pt: number for (let ix = 0, len = x.length; ix < len; ix++) { void (pt = x.charCodeAt(ix)) - if (pt === 34 || pt === 92 || pt < 32) { + if (pt === 34 /* " */ || pt === 92 /* \\ */ || pt < 32) { void (out += x.slice(prev, ix) + ESC_CHAR[pt]) void (prev = ix + 1) } else if (0xdfff <= pt && pt <= 0xdfff) { @@ -79,7 +78,7 @@ export function escape(x: string): string { } } void (out += x.slice(prev, ix) + "\\u" + pt.toString(16)) - void (prev = ix + 1) + void (prev = ix + 2) } } void (out += x.slice(prev)) @@ -87,10 +86,31 @@ export function escape(x: string): string { } export declare namespace parseKey { - type Options = Partial<{ parseAsJson: boolean }> + type Options = Partial<{ + parseAsJson: boolean + /** + * ## {@link defaultOptions.quotationMarks `Options.quotationMarks`} + * + * The character to use as a quotation mark if the interpolated key + * is not a valid identifier. + * + * @example + * import { parseKey } from '@traversable/registry' + * + * parseKey('a') + * // => 'a' + * + * parseKey(100) + * // => "100" + * + * parseKey(100, { quotationMarks: '`' }) + * // => `100` + */ + quotationMarks: '`' | '"' | `'` + }> } -/** +/** * ## {@link parseKey `parseKey`} */ export function parseKey( @@ -101,19 +121,24 @@ export function parseKey( export function parseKey( k: keyof any, { parseAsJson = parseKey.defaults.parseAsJson, + quotationMarks: quote = parseKey.defaults.quotationMarks, }: parseKey.Options = parseKey.defaults, _str = globalThis.String(k) ) { return ( typeof k === "symbol" ? _str : isQuoted(k) ? escape(_str) - : parseAsJson ? `"` + escape(_str) + `"` + : parseAsJson ? quote + escape(_str) + quote : isValidIdentifier(k) ? escape(_str) - : `"` + escape(_str) + `"` + : quote + escape(_str) + quote ) } -parseKey.defaults = { +let defaultOptions = { + /** Type defined in {@link parseKey.Options} */ parseAsJson: false, + /** Type defined in {@link parseKey.Options} */ + quotationMarks: '"', } satisfies Required +parseKey.defaults = defaultOptions diff --git a/packages/registry/src/pick.ts b/packages/registry/src/pick.ts index 58de5cab..78e0a4b7 100644 --- a/packages/registry/src/pick.ts +++ b/packages/registry/src/pick.ts @@ -39,6 +39,7 @@ export declare namespace omit { } export function pick(x: T, ks: K[]): pick +export function pick(x: T, ks: K[]): pick.Lax export function pick(x: { [x: keyof any]: unknown }, ks: (keyof any)[]) { if (!x || typeof x !== 'object') return x let allKeys = Object.keys(x) diff --git a/packages/registry/src/types.ts b/packages/registry/src/types.ts index 00f801d5..b7a56a52 100644 --- a/packages/registry/src/types.ts +++ b/packages/registry/src/types.ts @@ -17,10 +17,10 @@ export type Key = `${T}` export type Autocomplete = T | (string & {}) export interface Etc { [x: string]: T[keyof T] } -export interface Record extends newtype<{ [P in K]+?: V } & { [x in string]+?: V }> { } +export interface Record extends newtype<{ [P in K]+?: V } & { [x in string]+?: V }> {} -export interface Array extends newtype { } -export interface ReadonlyArray extends newtype { } +export interface Array extends newtype {} +export interface ReadonlyArray extends newtype {} export type Integer> = [T] extends [number] ? [Z] extends [`${number}.${string}`] ? never : number : never @@ -28,11 +28,26 @@ export type Integer> = [T] extends [number] // transforms export type Force = never | { -readonly [K in keyof T]: T[K] } export type Intersect = T extends readonly [infer Head, ...infer Tail] ? Intersect : Out +export type Require = [K] extends [never] + ? never | { [K in keyof T]-?: T[K] } + : never | ( + & { [P in keyof T as P extends K ? P : never]-?: T[P] } + & { [P in keyof T as P extends K ? never : P]+?: T[P] } + ) + +export type Partial = [K] extends [never] + ? never | { [K in keyof T]+?: T[K] } + : never | ( + & { [P in keyof T as P extends K ? P : never]-?: T[P] } + & { [P in keyof T as P extends K ? never : P]+?: T[P] } + ) export type PickIfDefined< T, K extends keyof any, - _ extends keyof T = K extends keyof T ? undefined extends T[K] ? never : K : never + _ extends + | K extends keyof T ? undefined extends T[K] ? never : K : never + = K extends keyof T ? undefined extends T[K] ? never : K : never > = never | { [K in _]: T[K] } // infererence @@ -45,20 +60,21 @@ export type Conform = Extract> = [_] exte export type Target = S extends (_: any) => _ is infer T ? T : never export type UnionToIntersection< - T, - U = (T extends T ? (contra: T) => void : never) extends (contra: infer U) => void ? U : never, -> = U + U, + Out = (U extends U ? (contra: U) => void : never) extends (contra: infer T) => void ? T : never, +> = Out -export type UnionToTuple extends () => infer X ? X : never> = UnionToTuple.loop<[], U, _> +export type UnionToTuple extends () => infer _ ? _ : never> = UnionToTuple.Loop export declare namespace UnionToTuple { - type loop extends () => infer X ? X : never> = [ + type Loop< U, - ] extends [never] - ? Todo - : loop<[_, ...Todo], Exclude> + Todo extends readonly unknown[], + _ = Contra extends () => infer X ? X : never + > = [U] extends [never] ? Todo : Loop, [_, ...Todo]> } -type Thunk = (U extends U ? (_: () => U) => void : never) extends (_: infer _) => void ? _ : never +/** @internal */ +type Contra = (U extends U ? (_: () => U) => void : never) extends (_: infer T) => void ? T : never export type Join< T, diff --git a/packages/schema-core/src/core.ts b/packages/schema-core/src/core.ts index 63a22d1b..aba76690 100644 --- a/packages/schema-core/src/core.ts +++ b/packages/schema-core/src/core.ts @@ -1,5 +1,5 @@ -import type { HKT, Functor as Functor_ } from '@traversable/registry' -import { fn, has, Object_assign, symbol, URI } from '@traversable/registry' +import type * as T from '@traversable/registry' +import { fn, has, symbol, URI } from '@traversable/registry' import type { Guarded, Schema } from './types.js' @@ -56,11 +56,23 @@ export type F = | tuple | object_<{ [x: string]: T }> +export declare namespace F { + type Unary = + | eq + | array + | record + | optional + | union + | intersect + | tuple + | object_<{ [x: string]: T }> +} + export type Fixpoint = | Leaf | Unary -export interface Free extends HKT { [-1]: F } +export interface Free extends T.HKT { [-1]: F } export type Leaf = typeof leaves[number] export type LeafTag = Leaf['tag'] @@ -70,6 +82,46 @@ export type Boundable = typeof boundables[number] export type BoundableTag = Boundable['tag'] export type Tag = typeof tags[number] export type UnaryTag = typeof unaryTags[number] +export type TypeName = T.TypeName + +interface NullaryCatalog { + never: never_ + any: any_ + unknown: unknown_ + void: void_ + null: null_ + undefined: undefined_ + symbol: symbol_ + boolean: boolean_ +} + +interface BoundableCatalog { + integer: integer + number: number_ + bigint: bigint_ + string: string_ +} + +interface UnaryCatalog { + eq: eq + optional: optional + array: array + record: record + union: union + intersect: intersect + tuple: tuple + object: object_<{ [x: string]: T }> +} + +export interface Catalog extends Catalog.Boundable, Catalog.Nullary, Catalog.Unary {} +export declare namespace Catalog { + export { + BoundableCatalog as Boundable, + NullaryCatalog as Nullary, + UnaryCatalog as Unary, + } +} + const hasTag = has('tag', (tag) => typeof tag === 'string') export const nullaries = [unknown_, never_, any_, void_, undefined_, null_, symbol_, boolean_] @@ -97,8 +149,18 @@ export const isCore: { } = ((u: unknown) => hasTag(u) && tags.includes(u.tag as never)) as never -export declare namespace Functor { type Index = (keyof any)[] } -export const Functor: Functor_ = { +export declare namespace Functor { + export type Algebra = T.Algebra + export type Index = (keyof any)[] + export type IndexedAlgebra = T.IndexedAlgebra + export { + F, + Free, + Fixpoint, + } +} + +export const Functor: T.Functor = { map(f) { return (x) => { switch (true) { @@ -117,7 +179,7 @@ export const Functor: Functor_ = { } } -export const IndexedFunctor: Functor_.Ix = { +export const IndexedFunctor: T.Functor.Ix = { ...Functor, mapWithIndex(f) { return (x, ix) => { diff --git a/packages/schema-core/src/exports.ts b/packages/schema-core/src/exports.ts index 14d18e7a..10cbec4c 100644 --- a/packages/schema-core/src/exports.ts +++ b/packages/schema-core/src/exports.ts @@ -51,6 +51,14 @@ export { export * as t from './namespace.js' export * from './extensions.js' +export type { + Boundable, + BoundableTag, + Nullary, + NullaryTag, + Unary, + UnaryTag, +} from './core.js' export * as recurse from './recursive.js' diff --git a/packages/schema-core/src/namespace.ts b/packages/schema-core/src/namespace.ts index 248464bc..15667d0e 100644 --- a/packages/schema-core/src/namespace.ts +++ b/packages/schema-core/src/namespace.ts @@ -25,13 +25,16 @@ export { of } from './schemas/of.js' export type { Boundable, + Catalog, F, Fixpoint, Free, Inline, Leaf, Tag, + TypeName, typeOf as typeof, + Unary, } from './core.ts' export { diff --git a/packages/schema-core/src/schemas/bigint.ts b/packages/schema-core/src/schemas/bigint.ts index f7d55261..a3d9ecfe 100644 --- a/packages/schema-core/src/schemas/bigint.ts +++ b/packages/schema-core/src/schemas/bigint.ts @@ -81,16 +81,19 @@ declare namespace bigint_ { = [Self] extends [{ maximum: bigint }] ? bigint_.between<[min: X, max: Self['maximum']]> : bigint_.min - ; + type Max = [Self] extends [{ minimum: bigint }] ? bigint_.between<[min: Self['minimum'], max: X]> : bigint_.max - ; + interface methods { - min(minimum: Min): bigint_.Min - max(maximum: Max): bigint_.Max - between(minimum: Min, maximum: Max): bigint_.between<[min: Min, max: Max]> + min(minimum: Min): bigint_.Min + max(maximum: Max): bigint_.Max + between( + minimum: Min, + maximum: Max + ): bigint_.between<[min: Min, max: Max]> } interface min extends bigint_ { minimum: Min } interface max extends bigint_ { maximum: Max } diff --git a/packages/schema-core/src/schemas/integer.ts b/packages/schema-core/src/schemas/integer.ts index 2969e5a1..d3492929 100644 --- a/packages/schema-core/src/schemas/integer.ts +++ b/packages/schema-core/src/schemas/integer.ts @@ -82,9 +82,12 @@ declare namespace integer { maximum?: number } interface methods { - min>(minimum: Min): integer.Min - max>(maximum: Max): integer.Max - between, Max extends Integer>(minimum: Min, maximum: Max): integer.between<[min: Min, max: Max]> + min>(minimum: Min): integer.Min + max>(maximum: Max): integer.Max + between, const Max extends Integer>( + minimum: Min, + maximum: Max + ): integer.between<[min: Min, max: Max]> } type Min = [Self] extends [{ maximum: number }] diff --git a/packages/schema-core/src/schemas/number.ts b/packages/schema-core/src/schemas/number.ts index 5972ae85..9bcd98a9 100644 --- a/packages/schema-core/src/schemas/number.ts +++ b/packages/schema-core/src/schemas/number.ts @@ -94,11 +94,14 @@ declare namespace number_ { exclusiveMaximum?: number } interface methods { - min(minimum: Min): number_.Min - max(maximum: Max): number_.Max - moreThan(moreThan: Min): ExclusiveMin - lessThan(lessThan: Max): ExclusiveMax - between(minimum: Min, maximum: Max): number_.between<[min: Min, max: Max]> + min(minimum: Min): number_.Min + max(maximum: Max): number_.Max + moreThan(moreThan: Min): ExclusiveMin + lessThan(lessThan: Max): ExclusiveMax + between( + minimum: Min, + maximum: Max + ): number_.between<[min: Min, max: Max]> } type Min = [Self] extends [{ exclusiveMaximum: number }] @@ -106,28 +109,28 @@ declare namespace number_ { : [Self] extends [{ maximum: number }] ? number_.between<[min: X, max: Self['maximum']]> : number_.min - ; + type Max = [Self] extends [{ exclusiveMinimum: number }] ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> : [Self] extends [{ minimum: number }] ? number_.between<[min: Self['minimum'], max: X]> : number_.max - ; + type ExclusiveMin = [Self] extends [{ exclusiveMaximum: number }] ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> : [Self] extends [{ maximum: number }] ? number_.maxStrictMin<[min: X, Self['maximum']]> : number_.moreThan - ; + type ExclusiveMax = [Self] extends [{ exclusiveMinimum: number }] ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> : [Self] extends [{ minimum: number }] ? number_.minStrictMax<[Self['minimum'], min: X]> : number_.lessThan - ; + interface min extends number_ { minimum: Min } interface max extends number_ { maximum: Max } interface moreThan extends number_ { exclusiveMinimum: Min } diff --git a/packages/schema-core/src/schemas/object.ts b/packages/schema-core/src/schemas/object.ts index ad856e41..42a396d1 100644 --- a/packages/schema-core/src/schemas/object.ts +++ b/packages/schema-core/src/schemas/object.ts @@ -39,6 +39,7 @@ namespace object_ { //<%= Definitions %> } as object_ export function def(xs: T, $?: Options, opt?: string[]): object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ /* v8 ignore next 1 */ export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { let userExtensions: Record = { diff --git a/packages/schema-core/src/schemas/string.ts b/packages/schema-core/src/schemas/string.ts index 4276ca17..4c66f2e4 100644 --- a/packages/schema-core/src/schemas/string.ts +++ b/packages/schema-core/src/schemas/string.ts @@ -79,20 +79,23 @@ declare namespace string_ { interface methods { minLength?: number maxLength?: number - min>(minLength: Min): string_.Min - max>(maxLength: Max): string_.Max - between, Max extends Integer>(minLength: Min, maxLength: Max): string_.between<[min: Min, max: Max]> + min>(minLength: Min): string_.Min + max>(maxLength: Max): string_.Max + between, const Max extends Integer>( + minLength: Min, + maxLength: Max + ): string_.between<[min: Min, max: Max]> } type Min = [Self] extends [{ maxLength: number }] ? string_.between<[min: Min, max: Self['maxLength']]> : string_.min - ; + type Max = [Self] extends [{ minLength: number }] ? string_.between<[min: Self['minLength'], max: Max]> : string_.max - ; + interface min extends string_ { minLength: Min } interface max extends string_ { maxLength: Max } interface between extends string_ { diff --git a/packages/schema-jit-compiler/README.md b/packages/schema-jit-compiler/README.md new file mode 100644 index 00000000..4e10e27d --- /dev/null +++ b/packages/schema-jit-compiler/README.md @@ -0,0 +1,64 @@ +
+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘀𝗰𝗵𝗲𝗺𝗮-𝗷𝗶𝘁-𝗰𝗼𝗺𝗽𝗶𝗹𝗲𝗿

+
+ +

+ This package contains the code for installing "JIT compiled" validators to your schemas. +

+ +
+ NPM Version +   + TypeScript +   + Static Badge +   + npm +   +
+ +
+ npm bundle size (scoped) +   + Static Badge +   + Static Badge +   +
+ +
+ Demo (StackBlitz) +   •   + TypeScript Playground +   •   + npm +
+
+
+
+ +## Getting started + +Users can consume this package in one of several ways: + +### Import side effect + module augmentation + +To install the `.compile` method on all schemas, simply import `@traversable/schema-jit-compiler/install`. + +Once you do, all schemas come with a `.compile` method you can use. + +### As a standalone function + +To compile a single schema, import `Jit` from `@traversable/schema-jit-compiler/recursive`, and pass the +schema you'd like to compile to `Jit.fromSchema`. + +### As a transitive dependency + +Schemas created using `@traversable/schema` include the `.compile` method out of the box, so if you've +installed that package, you don't need to do anything extra to take advantage of this feature. + +If you don't need this feature and would prefer not to have it installed, you can either: + +1. use a lower-level package like `@traversable/schema-core`, which does not install the `.compile` method + +2. configure `@traversable/schema` to not include it when building your schemas diff --git a/packages/schema-jit-compiler/package.json b/packages/schema-jit-compiler/package.json new file mode 100644 index 00000000..af2bfe3a --- /dev/null +++ b/packages/schema-jit-compiler/package.json @@ -0,0 +1,47 @@ +{ + "name": "@traversable/schema-jit-compiler", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/schema-jit-compiler" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^" + }, + "devDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^", + "@traversable/schema-seed": "workspace:^" + } +} diff --git a/packages/schema-jit-compiler/src/__generated__/__manifest__.ts b/packages/schema-jit-compiler/src/__generated__/__manifest__.ts new file mode 100644 index 00000000..64ff05e4 --- /dev/null +++ b/packages/schema-jit-compiler/src/__generated__/__manifest__.ts @@ -0,0 +1,46 @@ +export default { + "name": "@traversable/schema-jit-compiler", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/schema-jit-compiler" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^" + }, + "devDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^" + } +} as const \ No newline at end of file diff --git a/packages/schema-jit-compiler/src/exports.ts b/packages/schema-jit-compiler/src/exports.ts new file mode 100644 index 00000000..e468f38b --- /dev/null +++ b/packages/schema-jit-compiler/src/exports.ts @@ -0,0 +1,30 @@ +export * from './version.js' +export type { + Algebra, + Index, +} from './functor.js' +export { + Functor, + defaultIndex, + indexAccessor, + keyAccessor, + fold, + makeFold, + makeFunctor, +} from './functor.js' +export { + jit, + jitJson, + compile, +} from './jit.js' +export * as Json from './json.js' +export { + getWeight as getJsonWeight, + sort as sortJson, +} from './json.js' +export { + print, + sort as sortSchema, + WeightByTypeName, +} from './sort.js' + diff --git a/packages/schema-jit-compiler/src/functor.ts b/packages/schema-jit-compiler/src/functor.ts new file mode 100644 index 00000000..c54ab8d1 --- /dev/null +++ b/packages/schema-jit-compiler/src/functor.ts @@ -0,0 +1,211 @@ +import type * as T from '@traversable/registry' +import { fn, isValidIdentifier, parseKey, symbol, URI } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +/** @internal */ +let isProp = t.union(t.string, t.number) + +export type F = + | t.Leaf + | t.eq + | t.array + | t.record + | t.optional + | t.union + | t.intersect + | t.tuple + | t.object<[k: string, v: T][]> + +export type Fixpoint = + | t.Leaf + | t.eq + | t.array + | t.record + | t.optional + | t.union + | t.intersect + | t.tuple + | t.object<[k: string, v: Fixpoint][]> + +export interface Free extends T.HKT { [-1]: F } + +export function keyAccessor(key: keyof any | undefined, $: Index) { + return typeof key === 'string' ? isValidIdentifier(key) ? $.isOptional + ? `?.${key}` + : `.${key}` + : `[${parseKey(key)}]` + : '' +} + +export function indexAccessor(index: keyof any | undefined, $: { isOptional?: boolean }) { + return typeof index === 'number' ? $.isOptional + ? `?.[${index}]` + : `[${index}]` + : '' +} + +export let defaultIndex: Index = { + siblingCount: 0, + offset: 2, + dataPath: [], + schemaPath: [], + varName: 'value', + isRoot: true, + isOptional: false, +} + +/** @internal */ +let map + : T.Functor['map'] + = (f) => function jitMap(xs) { + switch (true) { + default: return fn.exhaustive(xs) + case t.isNullary(xs): return xs + case t.isBoundable(xs): return xs + case xs.tag === URI.eq: return t.eq(xs.def as never) + case xs.tag === URI.optional: return t.optional.def(f(xs.def)) + case xs.tag === URI.array: return t.array.def(f(xs.def)) + case xs.tag === URI.record: return t.record.def(f(xs.def)) + case xs.tag === URI.union: return t.union.def(xs.def.map(f)) + case xs.tag === URI.intersect: return t.intersect.def(xs.def.map(f)) + case xs.tag === URI.tuple: return t.tuple.def(xs.def.map(f)) + case xs.tag === URI.object: return t.object.def( + xs.def.map(([k, v]) => [k, f(v)] satisfies [any, any]), + undefined, + xs.opt, + ) + } + } + +export function makeFunctor(updateIndex: UpdateIndex): T.Functor.Ix { + return { + map, + mapWithIndex(f) { + return function jitMap(xs, ix) { + switch (true) { + default: return fn.exhaustive(xs) + case t.isNullary(xs): return xs + case t.isBoundable(xs): return xs + case xs.tag === URI.eq: return t.eq(xs.def as never) + case xs.tag === URI.optional: return t.optional.def(f(xs.def, updateIndex(ix, xs, [symbol.optional]))) + case xs.tag === URI.array: return t.array.def(f(xs.def, updateIndex(ix, xs, [symbol.array]))) + case xs.tag === URI.record: return t.record.def(f(xs.def, updateIndex(ix, xs, [symbol.record]))) + case xs.tag === URI.union: return t.union.def(xs.def.map((x, i) => f(x, updateIndex(ix, xs, [symbol.union, i])))) + case xs.tag === URI.intersect: return t.intersect.def(xs.def.map((x, i) => f(x, updateIndex(ix, xs, [symbol.intersect, i])))) + case xs.tag === URI.tuple: return t.tuple.def(xs.def.map((x, i) => f(x, updateIndex(ix, xs, [i])))) + case xs.tag === URI.object: return t.object.def( + xs.def.map(([k, v]) => [k, f(v, updateIndex(ix, xs, [k]))] satisfies [any, any]), + undefined, + xs.opt, + ) + } + } + } + } +} + +export function makeFold( + algebra: Algebra, + updateIndex: UpdateIndex +): (term: F, ix: Opts) => T + +export function makeFold( + algebra: Algebra, + updateIndex: UpdateIndex +): (term: F, ix: Index) => T { + return fn.cataIx(makeFunctor(updateIndex))(algebra) +} + +export type Index = { + siblingCount: number + offset: number + dataPath: (string | number)[] + isOptional: boolean + isRoot: boolean + schemaPath: (keyof any)[] + varName: string +} + +export type Algebra = T.IndexedAlgebra +export type UpdateIndex = (prev: Opts, x: F, i: (keyof any)[]) => Opts + +export interface Functor extends T.Functor.Ix {} +export declare namespace Functor { export { Algebra, Index } } + +export let Functor = makeFunctor((prev: Index, x, ix) => { + switch (true) { + default: return fn.exhaustive(x) + case t.isNullary(x): return prev + case t.isBoundable(x): return prev + case x.tag === URI.eq: return prev + case x.tag === URI.optional: return { + dataPath: prev.dataPath, + offset: prev.offset + 2, + isOptional: true, + isRoot: false, + schemaPath: [...prev.schemaPath, symbol.optional], + siblingCount: 0, + varName: prev.varName, + } + case x.tag === URI.array: return { + dataPath: prev.dataPath, + offset: prev.offset + 2, + isOptional: prev.isOptional, + isRoot: false, + schemaPath: [...prev.schemaPath, symbol.array], + siblingCount: 0, + varName: 'value', + } + case x.tag === URI.record: return { + dataPath: prev.dataPath, + offset: prev.offset + 2, + isOptional: prev.isOptional, + isRoot: false, + schemaPath: [...prev.schemaPath, symbol.record], + siblingCount: 0, + varName: prev.varName, + } + case x.tag === URI.union: return { + dataPath: prev.dataPath, + offset: prev.offset + 2, + isOptional: prev.isOptional, + isRoot: false, + schemaPath: [...prev.schemaPath, symbol.union, ...ix], + siblingCount: Math.max(x.def.length - 1, 0), + varName: prev.varName, + } + case x.tag === URI.intersect: return { + dataPath: prev.dataPath, + offset: prev.offset + 2, + isOptional: prev.isOptional, + isRoot: false, + schemaPath: [...prev.schemaPath, symbol.intersect, ...ix], + siblingCount: Math.max(x.def.length - 1, 0), + varName: prev.varName, + } + case x.tag === URI.tuple: return { + dataPath: [...prev.dataPath, ...ix.filter(isProp)], + offset: prev.offset + 2, + isOptional: prev.isOptional, + isRoot: false, + schemaPath: [...prev.schemaPath, ...ix], + siblingCount: Math.max(x.def.length - 1, 0), + varName: prev.varName + indexAccessor(ix[0], prev), + } + case x.tag === URI.object: { + return { + dataPath: [...prev.dataPath, ...ix.filter(isProp)], + offset: prev.offset + 2, + isOptional: prev.isOptional, + isRoot: false, + schemaPath: [...prev.schemaPath, ...ix], + siblingCount: Math.max(Object.keys(x.def).length - 1, 0), + varName: prev.varName + keyAccessor(ix[0], prev), + } + } + } +}) + +export let fold + : (algebra: T.IndexedAlgebra) => (schema: S, index?: Index) => T + = (algebra) => (schema, index = defaultIndex) => fn.cataIx(Functor)(algebra)(schema as never, index) diff --git a/packages/schema-jit-compiler/src/index.ts b/packages/schema-jit-compiler/src/index.ts new file mode 100644 index 00000000..410a4bcb --- /dev/null +++ b/packages/schema-jit-compiler/src/index.ts @@ -0,0 +1 @@ +export * from './exports.js' diff --git a/packages/schema-jit-compiler/src/jit.ts b/packages/schema-jit-compiler/src/jit.ts new file mode 100644 index 00000000..ae9f8a0f --- /dev/null +++ b/packages/schema-jit-compiler/src/jit.ts @@ -0,0 +1,362 @@ +import type * as T from '@traversable/registry' +import { escape, fn, getConfig, parseKey, URI } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +import * as Weighted from './sort.js' +import * as Json from './json.js' +import type { Index } from './functor.js' + +export type Context = { + VAR: string + RETURN: string + TABSTOP: string + JOIN: string + indent(numberOfSpaces: number): string + dedent(numberOfSpaces: number): string + join(numberOfSpaces: number): string +} + +export let MAX_WIDTH = 120 + +export function buildContext(ix: T.Require): Context { + let VAR = ix.varName + let indent = (numberOfSpaces: number) => `\r${' '.repeat(Math.max(ix.offset + numberOfSpaces, 0))}` + let dedent = (numberOfSpaces: number) => `\r${' '.repeat(Math.max(ix.offset - numberOfSpaces, 0))}` + let join = (numberOfSpaces: number) => indent(numberOfSpaces) + '&& ' + let JOIN = join(2) + let RETURN = indent(2) + let TABSTOP = indent(4) + return { dedent, indent, join, JOIN, RETURN, TABSTOP, VAR } +} + +export namespace Jit { + export declare namespace checkJson { + type ReturnTypeLowerBound = { + tag: URI.bottom | URI.array | URI.object + def: unknown + } + } + + export function checkJson(x: Json.IR>, ix: Json.Index): Json.IR + export function checkJson(x: Json.IR>, ix: Json.Index): checkJson.ReturnTypeLowerBound { + let { VAR, join } = buildContext(ix) + switch (true) { + default: return fn.exhaustive(x) + case x.tag === URI.bottom: { + let BODY = VAR + ' === ' + switch (true) { + default: return fn.exhaustive(x.def) + case x.def === null: BODY += 'null'; break + case x.def === undefined: BODY += 'undefined'; break + case x.def === true: BODY += 'true'; break + case x.def === false: BODY += 'false'; break + case x.def === 0: BODY += 1 / x.def === Number.NEGATIVE_INFINITY ? '-0' : '+0'; break + case typeof x.def === 'string': BODY += `"${escape(x.def)}"`; break + case typeof x.def === 'number': BODY += String(x.def); break + } + return { + tag: URI.bottom, + def: BODY, + } + } + + case x.tag === URI.array: return { + tag: URI.array, + def: '' + + `Array.isArray(${VAR}) && ` + + `${VAR}.length === ${x.def.length}` + + (x.def.length === 0 ? '' : x.def.map((v, i) => i === 0 ? join(0) + v.def : v.def).join(join(0))), + } + + case x.tag === URI.object: return { + tag: URI.object, + def: '' + + `!!${VAR} && typeof ${VAR} === "object" && !Array.isArray(${VAR})` + + (x.def.length === 0 ? '' : x.def.map(([, v], i) => i === 0 ? join(0) + v.def : v.def).join(join(0))), + } + } + } + + export let check: Weighted.Algebra = (x, ix) => { + let ctx = buildContext(ix) + let { VAR, indent } = ctx + let { schema: $ } = getConfig() + let NON_ARRAY_CHECK = $.treatArraysAsObjects ? '' : ` && !Array.isArray(${VAR})` + let IS_EXACT_OPTIONAL = $.optionalTreatment === 'exactOptional' + switch (true) { + default: return fn.exhaustive(x) + case x.tag === URI.never: return 'false' + case x.tag === URI.any: return 'true' + case x.tag === URI.unknown: return 'true' + case x.tag === URI.void: return `${VAR} === void 0` + case x.tag === URI.null: return `${VAR} === null` + case x.tag === URI.undefined: return `${VAR} === undefined` + case x.tag === URI.symbol: return `typeof ${VAR} === "symbol"` + case x.tag === URI.boolean: return `typeof ${VAR} === "boolean"` + + case x.tag === URI.integer: { + let CHECK = `Number.isSafeInteger(${VAR})` + let MIN_CHECK = t.number(x.minimum) ? ` && ${x.minimum} <= ${VAR}` : '' + let MAX_CHECK = t.number(x.maximum) ? ` && ${VAR} <= ${x.maximum}` : '' + let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' + let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' + return '' + + OPEN + + CHECK + + MIN_CHECK + + MAX_CHECK + + CLOSE + } + + case x.tag === URI.bigint: { + let CHECK = `typeof ${VAR} === "bigint"` + let MIN_CHECK = t.bigint(x.minimum) ? ` && ${x.minimum}n <= ${VAR}` : '' + let MAX_CHECK = t.bigint(x.maximum) ? ` && ${VAR} <= ${x.maximum}n` : '' + let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' + let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' + return '' + + OPEN + + CHECK + + MIN_CHECK + + MAX_CHECK + + CLOSE + } + + case x.tag === URI.number: { + let CHECK = `Number.isFinite(${VAR})` + let MIN_CHECK = t.number(x.exclusiveMinimum) ? ` && ${x.exclusiveMinimum} < ${VAR}` : t.number(x.minimum) ? ` && ${x.minimum} <= ${VAR}` : '' + let MAX_CHECK = t.number(x.exclusiveMaximum) ? ` && ${VAR} < ${x.exclusiveMaximum}` : t.number(x.maximum) ? ` && ${VAR} <= ${x.maximum}` : '' + let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' + let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' + return '' + + OPEN + + CHECK + + MIN_CHECK + + MAX_CHECK + + CLOSE + } + + case x.tag === URI.string: { + let CHECK = `typeof ${VAR} === "string"` + let MIN_CHECK = t.number(x.minLength) ? ` && ${x.minLength} <= ${VAR}.length` : '' + let MAX_CHECK = t.number(x.maxLength) ? ` && ${VAR}.length <= ${x.maxLength}` : '' + let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' + let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' + return '' + + OPEN + + CHECK + + MIN_CHECK + + MAX_CHECK + + CLOSE + } + + case x.tag === URI.eq: { + return jitJson( + x.def, { + ...ix, + varName: VAR, + offset: ix.offset + 2, + }) + } + + case x.tag === URI.optional: { + if (IS_EXACT_OPTIONAL) return x.def + else { + let CHECK = `${VAR} === undefined` + let WIDTH = ix.offset + CHECK.length + ' || '.length + x.def.length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let OPEN = SINGLE_LINE ? '(' : ('(' + indent(2)) + let CLOSE = SINGLE_LINE ? ')' : (indent(0) + ')') + let BODY = SINGLE_LINE ? (CHECK + ' || ' + x.def) : (CHECK + indent(0) + '|| ' + x.def) + return '' + + OPEN + + BODY + + CLOSE + } + } + + case x.tag === URI.array: { + let MIN_CHECK = t.number(x.minLength) ? `&& ${x.minLength} < ${VAR}.length` : '' + let MAX_CHECK = t.number(x.maxLength) ? `&& ${VAR}.length < ${x.maxLength}` : '' + let OUTER_CHECK = `Array.isArray(${VAR})${MIN_CHECK}${MAX_CHECK} && ` + let INNER_CHECK = `${VAR}.every((value) => ` + let WIDTH = ix.offset + OUTER_CHECK.length + INNER_CHECK.length + x.def.length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let OPEN = SINGLE_LINE ? '' : indent(4) + let CLOSE = SINGLE_LINE ? ')' : (indent(2) + ')') + // let OUTER_OPEN = SINGLE_LINE ? '' : indent(2) + // let OUTER_CLOSE = SINGLE_LINE ? '' : indent(0) + return '' + + OUTER_CHECK + + INNER_CHECK + + OPEN + + x.def + + CLOSE + } + + case x.tag === URI.record: { + let OUTER_CHECK = '' + + `!!${VAR} && typeof ${VAR} === "object"${NON_ARRAY_CHECK} ${indent(2)}&& ` + + `!(${VAR} instanceof Date) && !(${VAR} instanceof Uint8Array) ${indent(2)}&& ` + let INNER_CHECK = `Object.entries(${VAR}).every(([key, value]) => ` + let KEY_CHECK = 'typeof key === "string" && ' + let WIDTH = ix.offset + OUTER_CHECK.length + INNER_CHECK.length + x.def.length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let OPEN = SINGLE_LINE ? KEY_CHECK : (indent(4) + KEY_CHECK) + let CLOSE = SINGLE_LINE ? ')' : (indent(2) + ')') + return '' + + OUTER_CHECK + + INNER_CHECK + + OPEN + + x.def + + CLOSE + } + + case x.tag === URI.union: { + let CHILD_COUNT = x.def.length + let WIDTH = ix.offset + x.def.join(' || ').length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let OPEN = SINGLE_LINE || ix.isRoot ? '(' : ('(' + indent(2)) + let CLOSE = SINGLE_LINE || ix.isRoot ? ')' : (indent(0) + ')') + let BODY = CHILD_COUNT === 0 ? 'false' + : SINGLE_LINE ? x.def.map((v) => '(' + v + ')').join(' || ') + : x.def.map((v) => '(' + v + ')').join(indent(2) + '|| ') + return '' + + OPEN + + BODY + + CLOSE + } + + case x.tag === URI.intersect: { + let CHILD_COUNT = x.def.length + let WIDTH = ix.offset + x.def.join(' || ').length + let SINGLE_LINE = WIDTH < MAX_WIDTH + + // let OPEN = CHILD_COUNT < 2 ? '' : SINGLE_LINE ? '(' : ('(' + indent(2)) + // let CLOSE = CHILD_COUNT < 2 ? '' : SINGLE_LINE ? ')' : (indent(0) + ')') + // let OPEN = CHILD_COUNT < 2 ? '' : '(' + // let CLOSE = CHILD_COUNT < 2 ? '' : ')' + + let OPEN = SINGLE_LINE ? '' : ('(' + indent(2)) + let CLOSE = SINGLE_LINE ? '' : (indent(0) + ')') + + let BODY = CHILD_COUNT === 0 ? 'true' + : SINGLE_LINE ? x.def.join(' && ') + : x.def.join(indent(2) + '&& ') + + // : SINGLE_LINE ? x.def.join(' && ') + // : x.def.join(indent(2) + '&& ') + + return '' + + OPEN + + BODY + + CLOSE + } + + case x.tag === URI.tuple: { + let CHILD_COUNT = x.def.length + let CHECK = `Array.isArray(${VAR}) && ${VAR}.length === ${CHILD_COUNT}` + let WIDTH = ix.offset + CHECK.length + x.def.join(' && ').length + let SINGLE_LINE = WIDTH < MAX_WIDTH + // let OPEN = SINGLE_LINE ? '' : ('(' + indent(2)) + // let CLOSE = SINGLE_LINE ? '' : (indent(0) + ')') + + let JOIN = SINGLE_LINE ? '' : indent(2) + let CHILDREN = CHILD_COUNT === 0 ? '' : x.def.map((v) => JOIN + (SINGLE_LINE ? ' && ' : '&& ') + v).join('') + return '' + // + OPEN + + CHECK + + CHILDREN + // + CLOSE + } + + case x.tag === URI.object: { + let CHILD_COUNT = x.def.length + let CHECK = `!!${VAR} && typeof ${VAR} === "object"${NON_ARRAY_CHECK}` + let OPTIONAL_KEYS = Array.of().concat(x.opt) + let CHILDREN = x.def.map( + ([k, v]) => IS_EXACT_OPTIONAL && OPTIONAL_KEYS.includes(k) + ? `(!Object.hasOwn(${VAR}, "${parseKey(k)}") || ${v})` + : v + ) + let WIDTH = ix.offset + CHECK.length + CHILDREN.join(' && ').length + + let SINGLE_LINE = WIDTH < MAX_WIDTH + // let OPEN = SINGLE_LINE ? '' : ('(' + indent(2)) + // let CLOSE = SINGLE_LINE ? '' : (indent(0) + ')') + let JOIN = SINGLE_LINE ? '' : indent(2) + let BODY = CHILD_COUNT === 0 ? '' : CHILDREN.map((v) => JOIN + (SINGLE_LINE ? ' && ' : '&& ') + v).join('') + + return '' + // + OPEN + + CHECK + + BODY + // + CLOSE + + // + `!!${VAR} && typeof ${VAR} === "object"${NON_ARRAY_CHECK}` + // + (x.def.length === 0 ? '' : JOIN + x.def.map( + // ([k, v]) => IS_EXACT_OPTIONAL && OPTIONAL_KEYS.includes(k) + // ? `(!Object.hasOwn(${VAR}, "${parseKey(k)}") || ${v})` + // : v + // ).join(JOIN)) + } + } + } + + +} + +export function jitJson(json: Json.Any, index?: Index): string +export function jitJson(json: Json.Any, index?: Index) { + return fn.pipe( + Json.sort(json), + (sorted) => Json.fold(Jit.checkJson)(sorted, index).def, + ) +} + +export let check = fn.flow( + Weighted.sort, + Weighted.fold(Jit.check), +) + +export let jit = (schema: t.Schema) => { + let BODY = check(schema).trim() + if (BODY.startsWith('(') && BODY.endsWith(')')) + void (BODY = BODY.slice(1, -1)) + + let SINGLE_LINE = BODY.length < MAX_WIDTH + let OPEN = SINGLE_LINE ? '' : `(\r${' '.repeat(4)}` + let CLOSE = SINGLE_LINE ? '' : `\r${' '.repeat(2)})` + + return ` + +function check(value) { + return ${OPEN}${BODY}${CLOSE} +} +` + .trim() +} + +export let jitParser = (schema: t.Schema) => { + let body = check(schema) + return ` + +function parse(value) { + function check(value) { + return ( + ${body} + ) + } + if (check(value)) return value + else throw Error("invalid input") +} + +` + .trim() +} + +export function compile(schema: S): ((x: S['_type'] | T.Unknown) => x is S['_type']) +export function compile(schema: t.Schema): Function { return globalThis.Function('value', 'return ' + check(schema)) } + +export function compileParser(schema: S): ((x: S['_type'] | T.Unknown) => S['_type']) +export function compileParser(schema: t.Schema): Function { return globalThis.Function('value', 'return ' + check(schema)) } diff --git a/packages/schema-jit-compiler/src/json.ts b/packages/schema-jit-compiler/src/json.ts new file mode 100644 index 00000000..c927b529 --- /dev/null +++ b/packages/schema-jit-compiler/src/json.ts @@ -0,0 +1,143 @@ +import type * as T from '@traversable/registry' +import { fn, isValidIdentifier, Object_entries, Object_values, URI } from '@traversable/registry' +import { Json } from '@traversable/json' + +import type { Index as Ix } from './functor.js' + +export let isScalar = Json.isScalar +export let isArray = Json.isArray +export let isObject = Json.isObject + +export let WeightByType = { + undefined: 1, + null: 2, + boolean: 4, + number: 8, + string: 16, + array: 128, + object: 256, +} as const + +export type Any = import('@traversable/json').Json +export type Unary = import('@traversable/json').Unary +export interface IRFree extends T.HKT { [-1]: IR } + +export type IR = + | { tag: URI.bottom, def: Json.Scalar } + | { tag: URI.array, def: T[] } + | { tag: URI.object, def: [k: string, v: T][] } + +export type IRFixpoint = + | { tag: URI.bottom, def: Json.Scalar } + | { tag: URI.array, def: IRFixpoint[] } + | { tag: URI.object, def: [k: string, v: IRFixpoint][] } + +export type Index = Omit +export type Algebra = T.IndexedAlgebra + +export let defaultIndex = { + dataPath: [], + isRoot: true, + offset: 2, + siblingCount: 0, + varName: 'value', +} satisfies Index + +let map + : T.Functor['map'] + = (f) => (xs) => { + switch (true) { + default: return fn.exhaustive(xs) + case xs.tag === URI.bottom: return xs + case xs.tag === URI.array: return { tag: xs.tag, def: fn.map(xs.def, f) } + case xs.tag === URI.object: return { + tag: xs.tag, + def: fn.map(xs.def, ([k, v]) => [k, f(v)] satisfies [any, any]), + } + } + } + +export let Functor: T.Functor.Ix = { + map, + mapWithIndex(f) { + return function mapFn(xs, ix) { + switch (true) { + default: return fn.exhaustive(xs) + case xs.tag === URI.bottom: return xs + case xs.tag === URI.array: return { + tag: xs.tag, def: fn.map(xs.def, (x, i) => f(x, { + dataPath: [...ix.dataPath, i], + isRoot: false, + offset: ix.offset + 2, + siblingCount: xs.def.length, + varName: ix.varName + `[${i}]`, + })) + } + case Json.isObject(xs): return { + tag: xs.tag, def: fn.map(xs.def, ([k, v]) => [k, f(v, { + dataPath: [...ix.dataPath, k], + isRoot: false, + offset: ix.offset + 2, + siblingCount: Object_values(xs).length, + varName: ix.varName + (isValidIdentifier(k) ? `.${k}` : `["${k}"]`), + })] satisfies [any, any]) + } + } + } + }, +} + +export let fold + : (algebra: T.IndexedAlgebra) => (json: IR, ix?: Index) => T + = (algebra) => (json, index = defaultIndex) => fn.cataIx(Functor)(algebra)(json as /* FIXME */ never, index) + +export let toIR = Json.fold((x) => { + switch (true) { + default: return fn.exhaustive(x) + case x === undefined: + case x === null: + case typeof x === 'boolean': + case typeof x === 'number': + case typeof x === 'string': return { tag: URI.bottom, def: x } + case Json.isArray(x): return { tag: URI.array, def: [...x] } + case Json.isObject(x): return { tag: URI.object, def: Object_entries(x) } + } +}) + +let aggregateWeights + : (acc: number, curr: T.Param) => number + = (acc, curr) => acc + getWeight(curr) + + +export let weightComparator: T.Comparator = (l, r) => { + let lw = getWeight(l) + let rw = getWeight(r) + return lw < rw ? -1 : rw < lw ? +1 : 0 +} + +export let getWeight = (x: Any): number => { + switch (true) { + default: return fn.exhaustive(x) + case x === undefined: return WeightByType.undefined + case x === null: return WeightByType.null + case typeof x === 'boolean': return WeightByType.boolean + case typeof x === 'number': return WeightByType.number + case typeof x === 'string': return WeightByType.string + case Json.isArray(x): return WeightByType.array + x.reduce(aggregateWeights, 0) + case Json.isObject(x): return WeightByType.object + Object_values(x).reduce(aggregateWeights, 0) + } +} + +let sortAlgebra: Algebra = (x) => { + switch (true) { + default: return fn.exhaustive(x) + case x.tag === URI.bottom: return x + case x.tag === URI.array: return { tag: URI.array, def: x.def.sort(weightComparator) } + case x.tag === URI.object: return { tag: URI.object, def: x.def.sort(([, l], [, r]) => weightComparator(l, r)) } + } +} + +export let sort = fn.flow( + toIR, + fold(sortAlgebra), +) diff --git a/packages/schema-jit-compiler/src/sort.ts b/packages/schema-jit-compiler/src/sort.ts new file mode 100644 index 00000000..19f0f1ef --- /dev/null +++ b/packages/schema-jit-compiler/src/sort.ts @@ -0,0 +1,212 @@ +import type * as T from '@traversable/registry' +import { fn, symbol, typeName, URI } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +import type { Index } from './functor.js' +import { defaultIndex, keyAccessor, indexAccessor } from './functor.js' + +export let WeightByTypeName = { + never: 0, + any: 10, + unknown: 20, + void: 30, + undefined: 40, + null: 50, + symbol: 60, + boolean: 70, + integer: 80, + bigint: 90, + number: 100, + string: 110, + optional: 120, + intersect: 130, + union: 140, + tuple: 150, + object: 160, + array: 170, + record: 180, + eq: 190, +} as const + +export interface Free extends T.HKT { [-1]: IR } +export type Algebra = T.IndexedAlgebra + +export type IR = + | t.Leaf + | t.eq + | t.array + | t.record + | t.optional + | t.union + | t.intersect + | t.tuple + | t.object<[k: string, T][]> + + +export let map: T.Functor['map'] = (f) => { + return (x) => { + switch (true) { + default: return fn.exhaustive(x) + case t.isNullary(x): return x + case t.isBoundable(x): return x + case x.tag === URI.eq: return t.eq.def(x.def as never) + case x.tag === URI.optional: return t.optional.def(f(x.def)) + case x.tag === URI.array: return t.array.def(f(x.def)) + case x.tag === URI.record: return t.record.def(f(x.def)) + case x.tag === URI.union: return t.union.def(fn.map(x.def, f)) + case x.tag === URI.intersect: return t.intersect.def(fn.map(x.def, f)) + case x.tag === URI.tuple: return t.tuple.def(fn.map(x.def, f)) + case x.tag === URI.object: return t.object.def( + fn.map(x.def, ([k, v]) => [k, f(v)] satisfies [any, any]), + undefined, + x.opt, + ) + } + } +} + +export let Functor: T.Functor.Ix = { + map, + mapWithIndex(f) { + return (xs, ix) => { + switch (true) { + default: return fn.exhaustive(xs) + case t.isNullary(xs): return xs + case t.isBoundable(xs): return xs + case xs.tag === URI.eq: return xs as never + case xs.tag === URI.optional: return t.optional.def(f(xs.def, { + dataPath: ix.dataPath, + isOptional: true, + isRoot: false, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, symbol.optional], + siblingCount: 0, + varName: ix.varName, + })) + case xs.tag === URI.array: return t.array.def(f(xs.def, { + dataPath: ix.dataPath, + isOptional: ix.isOptional, + isRoot: false, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, symbol.array], + siblingCount: 0, + varName: 'value', + })) + case xs.tag === URI.record: return t.record.def(f(xs.def, { + dataPath: ix.dataPath, + isOptional: ix.isOptional, + isRoot: false, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, symbol.array], + siblingCount: 0, + varName: 'value', + })) + case xs.tag === URI.union: return t.union.def(fn.map(xs.def, (x, i) => f(x, { + dataPath: ix.dataPath, + isOptional: ix.isOptional, + isRoot: false, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, i], + siblingCount: Math.max(xs.def.length - 1, 0), + varName: ix.varName, + }))) + case xs.tag === URI.intersect: return t.intersect.def(fn.map(xs.def, (x, i) => f(x, { + dataPath: ix.dataPath, + isOptional: ix.isOptional, + isRoot: false, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, i], + siblingCount: Math.max(xs.def.length - 1, 0), + varName: ix.varName, + }))) + case xs.tag === URI.tuple: return t.tuple.def(fn.map(xs.def, (x, i) => f(x, { + dataPath: [...ix.dataPath, i], + isOptional: ix.isOptional, + isRoot: false, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, i], + siblingCount: Math.max(xs.def.length - 1, 0), + varName: ix.varName + indexAccessor(i, ix), + }))) + case xs.tag === URI.object: { + return t.object.def( + fn.map(xs.def, ([k, v]) => [k, f(v, { + dataPath: [...ix.dataPath, k], + isOptional: ix.isOptional, + isRoot: false, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, k], + siblingCount: Math.max(Object.keys(xs.def).length - 1, 0), + varName: ix.varName + keyAccessor(k, ix), + })] satisfies [any, any]), + undefined, + xs.opt, + ) + } + } + } + }, +} + +export let fold = (algebra: Algebra) => (x: IR) => fn.cataIx(Functor)(algebra)(x, defaultIndex) +export let print = fold((x) => t.isNullary(x) ? x.tag : t.isBoundable(x) ? x.tag : x.def) +export let toIR = t.fold( + (x) => x.tag !== URI.object ? x : t.object.def( + Object.entries(x.def), + undefined, + Array.of().concat(x.opt), + ) +) + +let aggregateWeights + : (acc: number, curr: t.Schema) => number + = (acc, curr) => Math.max(acc, getWeight(curr)) + +let weightComparator: T.Comparator = (l, r) => { + let lw = getWeight(l) + let rw = getWeight(r) + return lw < rw ? -1 : rw < lw ? +1 : 0 +} + +function getWeight(x: IR): number +function getWeight(x: t.Schema): number +function getWeight(x: IR): number { + let w = WeightByTypeName[typeName(x)] + switch (true) { + default: return fn.exhaustive(x) + case t.isNullary(x): return w + case t.isBoundable(x): return w + case x.tag === URI.eq: return w + case x.tag === URI.optional: return w + getWeight(x.def) + case x.tag === URI.array: return w + getWeight(x.def) + case x.tag === URI.record: return w + getWeight(x.def) + case x.tag === URI.union: return w + x.def.reduce(aggregateWeights, 0) + case x.tag === URI.intersect: return w + x.def.reduce(aggregateWeights, 0) + case x.tag === URI.tuple: return w + x.def.reduce(aggregateWeights, 0) + case x.tag === URI.object: return w + x.def.map(([, v]) => v).reduce(aggregateWeights, 0) + } +} + +let sortAlgebra: Algebra = (x) => { + switch (true) { + default: return fn.exhaustive(x) + case t.isNullary(x): return x + case t.isBoundable(x): return x + case x.tag === URI.eq: return x + case x.tag === URI.optional: return t.optional.def(x.def) + case x.tag === URI.array: return t.array.def(x.def) + case x.tag === URI.record: return t.record.def(x.def) + case x.tag === URI.union: return t.union.def(x.def.sort(weightComparator)) + case x.tag === URI.intersect: return t.intersect.def([...x.def].sort(weightComparator)) + case x.tag === URI.tuple: return t.tuple.def(x.def.sort(weightComparator)) + case x.tag === URI.object: return t.object.def( + x.def.sort(([, l], [, r]) => weightComparator(l, r)), + undefined, + x.opt, + ) + } +} + +export let sort + : (schema: t.Schema) => IR + = fn.flow(toIR, fold(sortAlgebra)) diff --git a/packages/schema-jit-compiler/src/version.ts b/packages/schema-jit-compiler/src/version.ts new file mode 100644 index 00000000..388bbc3e --- /dev/null +++ b/packages/schema-jit-compiler/src/version.ts @@ -0,0 +1,3 @@ +import pkg from './__generated__/__manifest__.js' +export const VERSION = `${pkg.name}@${pkg.version}` as const +export type VERSION = typeof VERSION \ No newline at end of file diff --git a/packages/schema-jit-compiler/test/TODO.ts b/packages/schema-jit-compiler/test/TODO.ts new file mode 100644 index 00000000..d08cf0a7 --- /dev/null +++ b/packages/schema-jit-compiler/test/TODO.ts @@ -0,0 +1,106 @@ +import { fc } from '@fast-check/vitest' + +import type { Bounds, TypeName } from '@traversable/registry' +import { fn, pick, typeName, URI } from '@traversable/registry' +import { t } from '@traversable/schema-core' +import type { BoundableTag, NullaryTag, UnaryTag } from '@traversable/schema-core' + +type NullaryType = TypeName +type BoundableType = TypeName +type UnaryType = TypeName +type GetType = never | t.Catalog[K]['_type'] +type GetBounds = never | Bounds +type GetInput = t.Catalog.Unary>[K] +type GetOutput[K]['_type']> = t.unknown extends T ? unknown : T +type BoundableKeys = + | 'minLength' + | 'maxLength' + | 'minimum' + | 'maximum' + +/** @internal */ +let empty = fc.constant(void 0 as never) + +type Nullary = never | { [K in NullaryType]: fc.Arbitrary> } +let Nullary = { + never: empty, + any: fc.anything() as fc.Arbitrary, + unknown: fc.anything(), + void: empty as fc.Arbitrary, + null: fc.constant(null), + undefined: fc.constant(undefined), + symbol: fc.string().map((_) => Symbol(_)), + boolean: fc.boolean(), +} as const satisfies Nullary + +type Boundable = never | { [K in BoundableType]: (bounds?: GetBounds) => fc.Arbitrary> } +let Boundable = { + integer: (b) => fc.integer({ min: b?.gte, max: b?.lte }), + bigint: (b) => fc.bigInt({ min: b?.gte, max: b?.lte }), + string: (b) => fc.string({ minLength: b?.gte, maxLength: b?.lte }), + number: (b) => fc.float({ + min: b?.gte, + max: b?.lte, + ...t.number(b?.gt) && { minExcluded: true, min: b.gt }, + ...t.number(b?.lt) && { maxExcluded: true, max: b.lt }, + }), +} as const satisfies Boundable + +type Unary = never | { [K in UnaryType]: (x: GetInput) => fc.Arbitrary> } +let Unary = { + eq: (x) => fc.constant(x.def), + optional: (x) => fc.option(x.def, { nil: void 0 }), + array: (x) => fc.array(x.def), + record: (x) => fc.dictionary(fc.string(), x.def), + union: (xs) => fc.oneof(...xs.def), + tuple: (xs) => fc.tuple(...xs.def), + intersect: (xs) => fc.tuple(...xs.def).map((arbs) => arbs.reduce((acc: {}, cur) => cur == null ? acc : Object.assign(acc, cur), {})), + object: (xs) => fc.record(xs.def, { ...[xs.opt].concat().length > 0 && { requiredKeys: Array.of().concat(xs.req) } }), +} as const satisfies Unary + +function getBounds(schema: S): Pick +function getBounds(schema: S): Pick +function getBounds(schema: S): Pick +function getBounds(schema: S): Pick +function getBounds(schema: S): Bounds +function getBounds(schema: t.Boundable) { + return pick(schema, ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum']) +} + +function fromSchemaAlgebra(options?: fromSchema.Options): t.Functor.Algebra> +function fromSchemaAlgebra(_?: fromSchema.Options): t.Functor.Algebra> { + return (x) => { + switch (true) { + default: return fn.exhaustive(x) + case t.isNullary(x): return Nullary[typeName(x)] + case t.isBoundable(x): return Boundable[typeName(x)](getBounds(x)) + case x.tag === URI.eq: return Unary.eq(x) + case x.tag === URI.optional: return Unary.optional(x) + case x.tag === URI.array: return Unary.array(x) + case x.tag === URI.record: return Unary.record(x) + case x.tag === URI.union: return Unary.union(x) + case x.tag === URI.intersect: return Unary.intersect(x) + case x.tag === URI.tuple: return Unary.tuple(x) + case x.tag === URI.object: return Unary.object(x) + } + } +} + +export let defaultOptions = { + +} satisfies Required + +declare namespace fromSchema { + type Options = { + + } +} + +/** + * ## {@link fromSchema `Arbitrary.fromSchema`} + */ +export let fromSchema + : (schema: S, options?: fromSchema.Options) => fc.Arbitrary + = (schema, options) => t.fold(fromSchemaAlgebra(options))(schema) + + ; (fromSchema as typeof fromSchema & { defaultOptions?: typeof defaultOptions }).defaultOptions = defaultOptions diff --git a/packages/schema-jit-compiler/test/functor.test.ts b/packages/schema-jit-compiler/test/functor.test.ts new file mode 100644 index 00000000..06d56615 --- /dev/null +++ b/packages/schema-jit-compiler/test/functor.test.ts @@ -0,0 +1,9 @@ +import * as vi from 'vitest' + +import { t, configure } from '@traversable/schema-core' +import { Functor, jit } from '@traversable/schema-jit-compiler' + +vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () => { + +}) + diff --git a/packages/schema-jit-compiler/test/jit.test.ts b/packages/schema-jit-compiler/test/jit.test.ts new file mode 100644 index 00000000..7e13c4de --- /dev/null +++ b/packages/schema-jit-compiler/test/jit.test.ts @@ -0,0 +1,1987 @@ +import * as vi from 'vitest' + +import { t, configure } from '@traversable/schema-core' +import { compile, jit, jitJson } from '@traversable/schema-jit-compiler' + +import { Seed } from '@traversable/schema-seed' +import { fc, test } from '@fast-check/vitest' +import * as Arbitrary from './TODO.js' + + +vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: property tests', () => { + + vi.describe.skip('〖⛳️〗‹‹ ❲compile❳: object', () => { + let schema = t.object({ + a: t.record(t.object({ + b: t.string, + c: t.tuple() + })) + }) + + let arbitrary = Arbitrary.fromSchema(schema) + // let check = compile(schema) + + test.prop([arbitrary], {})('〖⛳️〗› ❲compile❳: object', (data) => { + // vi.assert.isTrue(check(data)) + }) + + }) + + vi.it('〖⛳️〗› ❲jit❳', () => { + + let s = t.object({ + + "#1C": t.object({ + + twoC: t.intersect( + t.object({ + '\\3A': t.optional(t.symbol), + '\\3B': t.optional(t.array(t.union(t.eq({ tag: 'left' }), t.eq({ tag: 'right' })))), + }), + t.object({ + g: t.tuple( + t.object({ h: t.any }) + ), + // h: t.optional(t.object({ i: t.optional(t.boolean), j: t.union(t.number.moreThan(0).max(128), t.bigint) })), + }) + ), + + // twoB: t.eq({ + // "#3B": [ + // 1, + // [2], + // [[3]], + // ], + // "#3A": { + // n: 'over 9000', + // o: [ + // { p: false }, + // ], + // } + // }), + + // twoA: t.integer.between(-10, 10), + + }), + + // "#1A": t.union(t.integer.min(3)), + + // "#1B": t.tuple( + // t.record(t.any), + // ), + + }) + + let j = jit(s) + + try { + let c = compile(s) + let a = Arbitrary.fromSchema(s) + let m = fc.sample(a, 1)[0] + + console.log(JSON.stringify(m, null, 2)) + console.log('test', c(m)) + + } catch (e) { + vi.assert.fail('' + + '\r\n\n' + + ( + t.has('message', t.string)(e) + ? e.message + : JSON.stringify(e, null, 2) + ) + + '\r\n\n' + + '\t' + + 'Function body:' + + '\r\n\n' + + j + + '\r\n' + ) + } + }) +}) + + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: compile(...)', () => { + + + + // vi.it('〖⛳️〗› ❲compile❳: object', () => { + + + // // vi.assert.isTrue( + // // check({ a: {} }) + // // ) + + + // }) + + let schema = t.object({ + a: t.record(t.object({ + b: t.string, + c: t.tuple() + })) + }) + + // let jitted = jit(schema) + + // let check = compile(schema) + + // vi.it('TMP', () => { + // vi.expect(jitted).toMatchInlineSnapshot(` + // "function check(value) { + // return ( + // !!value && typeof value === 'object' && !Array.isArray(value) + // && !!value.a && typeof value.a === 'object' && !Array.isArray(value.a) + // && !(value.a instanceof Date) && !(value.a instanceof Uint8Array) + // && Object.entries(value.a).every( + // ([key, value]) => typeof key === 'string' ? !!value && typeof value === 'object' && !Array.isArray(value) + // && typeof value.b === 'string' + // && Array.isArray(value.c) && value.c.length === 0 : true + // ) + // ) + // }" + // `) + // }) + + // vi.test.concurrent.for([ + // // FAILURE + // {}, + // { a: [] }, + // { a: { record: { b: '' } } }, + // { a: { record: { b: '', c: [1] } } }, + // ])('Validation fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + + // vi.test.concurrent.for([ + // // SUCCESS + // { a: {} }, + // { a: { record: { b: '', c: [] } } }, + // ])('Validation succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + +}) + + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: jitJson', () => { + + vi.it('〖⛳️〗› ❲jitJson❳: bad input', () => { + /* @ts-expect-error */ + vi.assert.throws(() => jitJson(Symbol())) + + /* @ts-expect-error */ + vi.assert.throws(() => jitJson(1n)) + }) + + vi.it('〖⛳️〗› ❲jitJson❳: null', () => { + vi.expect(jitJson( + null + )).toMatchInlineSnapshot + + (`"value === null"`) + }) + + vi.it('〖⛳️〗› ❲jitJson❳: undefined', () => { + vi.expect(jitJson( + undefined + )).toMatchInlineSnapshot + (`"value === undefined"`) + }) + + vi.it('〖⛳️〗› ❲jitJson❳: booleans', () => { + vi.expect(jitJson( + false + )).toMatchInlineSnapshot + (`"value === false"`) + + vi.expect(jitJson( + true + )).toMatchInlineSnapshot + (`"value === true"`) + }) + + vi.it('〖⛳️〗› ❲jitJson❳: numbers', () => { + vi.expect(jitJson( + Number.MIN_SAFE_INTEGER + )).toMatchInlineSnapshot + (`"value === -9007199254740991"`) + + vi.expect(jitJson( + Number.MAX_SAFE_INTEGER + )).toMatchInlineSnapshot + (`"value === 9007199254740991"`) + + vi.expect(jitJson( + +0 + )).toMatchInlineSnapshot + (`"value === +0"`) + + vi.expect(jitJson( + -0 + )).toMatchInlineSnapshot + (`"value === -0"`) + + vi.expect(jitJson( + 1 / 3 + )).toMatchInlineSnapshot + (`"value === 0.3333333333333333"`) + + vi.expect(jitJson( + -1 / 3 + )).toMatchInlineSnapshot + (`"value === -0.3333333333333333"`) + + vi.expect(jitJson( + 1e+21 + )).toMatchInlineSnapshot + (`"value === 1e+21"`) + + vi.expect(jitJson( + -1e+21 + )).toMatchInlineSnapshot + (`"value === -1e+21"`) + + vi.expect(jitJson( + 1e-21 + )).toMatchInlineSnapshot + (`"value === 1e-21"`) + + vi.expect(jitJson( + -1e-21 + )).toMatchInlineSnapshot + (`"value === -1e-21"`) + }) + + vi.it('〖⛳️〗› ❲jitJson❳: strings', () => { + vi.expect(jitJson( + '' + )).toMatchInlineSnapshot + (`"value === ''"`) + + vi.expect(jitJson( + '\\' + )).toMatchInlineSnapshot + (`"value === '\\\\'"`) + }) + + vi.it('〖⛳️〗› ❲jitJson❳: objects', () => { + vi.expect(jitJson( + {} + )).toMatchInlineSnapshot + (`"!!value && typeof value === 'object' && !Array.isArray(value)"`) + + vi.expect(jitJson( + { + m: { o: 'O' }, + l: ['L'] + } + )).toMatchInlineSnapshot + (` + "!!value && typeof value === 'object' && !Array.isArray(value) + && Array.isArray(value.l) && value.l.length === 1 + && value.l[0] === 'L' + && !!value.m && typeof value.m === 'object' && !Array.isArray(value.m) + && value.m.o === 'O'" + `) + + }) + + vi.it('〖⛳️〗› ❲jitJson❳: arrays', () => { + vi.expect(jitJson( + [] + )).toMatchInlineSnapshot + (`"Array.isArray(value) && value.length === 0"`) + + vi.expect(jitJson( + [1, 2, 3] + )).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 3 + && value[0] === 1 + && value[1] === 2 + && value[2] === 3" + `) + + vi.expect(jitJson( + [[11], [22], [33]] + )).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 3 + && Array.isArray(value[0]) && value[0].length === 1 + && value[0][0] === 11 + && Array.isArray(value[1]) && value[1].length === 1 + && value[1][0] === 22 + && Array.isArray(value[2]) && value[2].length === 1 + && value[2][0] === 33" + `) + + vi.expect(jitJson( + [ + { + a: 3, + b: 3, + c: [5, 6] + }, + { z: 2 }, + 1, + ] + )).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 3 + && value[0] === 1 + && !!value[1] && typeof value[1] === 'object' && !Array.isArray(value[1]) + && value[1].z === 2 + && !!value[2] && typeof value[2] === 'object' && !Array.isArray(value[2]) + && value[2].a === 3 + && value[2].b === 3 + && Array.isArray(value[2].c) && value[2].c.length === 2 + && value[2].c[0] === 5 + && value[2].c[1] === 6" + `) + + vi.expect(jitJson( + [ + { THREE: [{ A: null, B: false }] }, + { FOUR: [{ A: 1, B: false }], C: '' }, + { TWO: [{ A: null, B: undefined }] }, + { ONE: [true] } + ] + )).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 4 + && !!value[0] && typeof value[0] === 'object' && !Array.isArray(value[0]) + && Array.isArray(value[0].ONE) && value[0].ONE.length === 1 + && value[0].ONE[0] === true + && !!value[1] && typeof value[1] === 'object' && !Array.isArray(value[1]) + && Array.isArray(value[1].TWO) && value[1].TWO.length === 1 + && !!value[1].TWO[0] && typeof value[1].TWO[0] === 'object' && !Array.isArray(value[1].TWO[0]) + && value[1].TWO[0].B === undefined + && value[1].TWO[0].A === null + && !!value[2] && typeof value[2] === 'object' && !Array.isArray(value[2]) + && Array.isArray(value[2].THREE) && value[2].THREE.length === 1 + && !!value[2].THREE[0] && typeof value[2].THREE[0] === 'object' && !Array.isArray(value[2].THREE[0]) + && value[2].THREE[0].A === null + && value[2].THREE[0].B === false + && !!value[3] && typeof value[3] === 'object' && !Array.isArray(value[3]) + && value[3].C === '' + && Array.isArray(value[3].FOUR) && value[3].FOUR.length === 1 + && !!value[3].FOUR[0] && typeof value[3].FOUR[0] === 'object' && !Array.isArray(value[3].FOUR[0]) + && value[3].FOUR[0].B === false + && value[3].FOUR[0].A === 1" + `) + + let modularArithmetic = (mod: number, operator: '+' | '*') => { + let index = mod, + row = Array.of(), + col = Array.of(), + matrix = Array.of() + while (index-- !== 0) void ( + row.push(index), + col.push(index), + matrix.push(Array.from({ length: mod })) + ) + for (let i = 0; i < row.length; i++) + for (let j = 0; j < col.length; j++) + matrix[i][j] = (operator === '+' ? i + j : i * j) % mod + // + return matrix + } + + let table = modularArithmetic(5, '*') + + vi.expect(table).toMatchInlineSnapshot + (` + [ + [ + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 1, + 2, + 3, + 4, + ], + [ + 0, + 2, + 4, + 1, + 3, + ], + [ + 0, + 3, + 1, + 4, + 2, + ], + [ + 0, + 4, + 3, + 2, + 1, + ], + ] + `) + + vi.expect(jitJson(table)).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 5 + && Array.isArray(value[0]) && value[0].length === 5 + && value[0][0] === +0 + && value[0][1] === +0 + && value[0][2] === +0 + && value[0][3] === +0 + && value[0][4] === +0 + && Array.isArray(value[1]) && value[1].length === 5 + && value[1][0] === +0 + && value[1][1] === 1 + && value[1][2] === 2 + && value[1][3] === 3 + && value[1][4] === 4 + && Array.isArray(value[2]) && value[2].length === 5 + && value[2][0] === +0 + && value[2][1] === 2 + && value[2][2] === 4 + && value[2][3] === 1 + && value[2][4] === 3 + && Array.isArray(value[3]) && value[3].length === 5 + && value[3][0] === +0 + && value[3][1] === 3 + && value[3][2] === 1 + && value[3][3] === 4 + && value[3][4] === 2 + && Array.isArray(value[4]) && value[4].length === 5 + && value[4][0] === +0 + && value[4][1] === 4 + && value[4][2] === 3 + && value[4][3] === 2 + && value[4][4] === 1" + `) + + }) +}) + + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nullary', () => { + + vi.it('〖⛳️〗› ❲jit❳: t.never', () => { + vi.expect(jit( + t.never + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + false + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.any', () => { + vi.expect(jit( + t.any + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + true + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.unknown', () => { + vi.expect(jit( + t.unknown + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + true + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.void', () => { + vi.expect(jit( + t.void + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + value === void 0 + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.null', () => { + vi.expect(jit( + t.null + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + value === null + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.undefined', () => { + vi.expect(jit( + t.undefined + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + value === undefined + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.symbol', () => { + vi.expect(jit( + t.symbol + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + typeof value === 'symbol' + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.boolean', () => { + vi.expect(jit( + t.boolean + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + typeof value === 'boolean' + ) + }" + `) + }) + +}) + + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: boundable', () => { + + vi.it('〖⛳️〗› ❲jit❳: t.integer', () => { + vi.expect(jit( + t.integer + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + Number.isSafeInteger(value) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.integer.min(x)', () => { + vi.expect(jit( + t.integer.min(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isSafeInteger(value) && 0 <= value) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.integer.max(x)', () => { + vi.expect(jit( + t.integer.max(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isSafeInteger(value) && value <= 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.integer.min(x).max(y)', () => { + vi.expect(jit( + t.integer + .min(0) + .max(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isSafeInteger(value) && 0 <= value && value <= 1) + ) + }" + `) + + vi.expect(jit( + t.integer + .max(1) + .min(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isSafeInteger(value) && 0 <= value && value <= 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.integer.between(x, y)', () => { + vi.expect(jit( + t.integer.between(0, 1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isSafeInteger(value) && 0 <= value && value <= 1) + ) + }" + `) + + vi.expect(jit( + t.integer.between(1, 0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isSafeInteger(value) && 0 <= value && value <= 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.bigint', () => { + vi.expect(jit( + t.bigint + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + typeof value === 'bigint' + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.bigint.min(x)', () => { + vi.expect(jit( + t.bigint.min(0n) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'bigint' && 0n <= value) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.bigint.max(x)', () => { + vi.expect(jit( + t.bigint.max(1n) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'bigint' && value <= 1n) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.bigint.min(x).max(y)', () => { + vi.expect(jit( + t.bigint + .min(0n) + .max(1n) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'bigint' && 0n <= value && value <= 1n) + ) + }" + `) + + vi.expect(jit( + t.bigint + .max(1n) + .min(0n) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'bigint' && 0n <= value && value <= 1n) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.bigint.between(x, y)', () => { + vi.expect(jit( + t.bigint.between(0n, 1n) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'bigint' && 0n <= value && value <= 1n) + ) + }" + `) + + vi.expect(jit( + t.bigint.between(1n, 0n) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'bigint' && 0n <= value && value <= 1n) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number', () => { + vi.expect(jit( + t.number + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + Number.isFinite(value) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number.min(x)', () => { + vi.expect(jit( + t.number.min(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 <= value) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number.max(x)', () => { + vi.expect(jit( + t.number.max(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && value <= 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number.min(x).max(y)', () => { + vi.expect(jit( + t.number + .min(0) + .max(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 <= value && value <= 1) + ) + }" + `) + + vi.expect(jit( + t.number + .max(1) + .min(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 <= value && value <= 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number.between(x, y)', () => { + vi.expect(jit( + t.number.between(0, 1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 <= value && value <= 1) + ) + }" + `) + + vi.expect(jit( + t.number.between(1, 0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 <= value && value <= 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number.moreThan(x)', () => { + vi.expect(jit( + t.number.moreThan(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 < value) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number.lessThan(x)', () => { + vi.expect(jit( + t.number.lessThan(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && value < 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number.moreThan(x).lessThan(y)', () => { + vi.expect(jit( + t.number + .moreThan(0) + .lessThan(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 < value && value < 1) + ) + }" + `) + + vi.expect(jit( + t.number + .lessThan(1) + .moreThan(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 < value && value < 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number.min(x).lessThan(y)', () => { + vi.expect(jit( + t.number + .min(0) + .lessThan(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 <= value && value < 1) + ) + }" + `) + + vi.expect(jit( + t.number + .lessThan(1) + .min(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 <= value && value < 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.number.moreThan(x).max(y)', () => { + vi.expect(jit( + t.number + .moreThan(0) + .max(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 < value && value <= 1) + ) + }" + `) + + vi.expect(jit( + t.number + .max(1) + .moreThan(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (Number.isFinite(value) && 0 < value && value <= 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.string', () => { + vi.expect(jit( + t.string + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + typeof value === 'string' + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.string.min(x)', () => { + vi.expect(jit( + t.string.min(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'string' && 0 <= value.length) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.string.max(x)', () => { + vi.expect(jit( + t.string.max(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'string' && value.length <= 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.string.min(x).max(y)', () => { + vi.expect(jit( + t.string + .min(0) + .max(1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'string' && 0 <= value.length && value.length <= 1) + ) + }" + `) + + vi.expect(jit( + t.string + .max(1) + .min(0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'string' && 0 <= value.length && value.length <= 1) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.string.between(x, y)', () => { + vi.expect(jit( + t.string.between(0, 1) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'string' && 0 <= value.length && value.length <= 1) + ) + }" + `) + + vi.expect(jit( + t.string.between(1, 0) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === 'string' && 0 <= value.length && value.length <= 1) + ) + }" + `) + }) +}) + + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary', () => { + + vi.it('〖⛳️〗› ❲jit❳: t.eq(...)', () => { + vi.expect(jit( + t.eq({ + l: 'L', + m: 'M' + }) + )).toMatchInlineSnapshot + (` + "function check(value) { + return !!value && typeof value === "object" && !Array.isArray(value) + && value.l === "L" + && value.m === "M" + }" + `) + + vi.expect(jit( + t.eq( + [ + { + a: 3, + b: 3, + c: [5, 6], + }, + { z: 2 }, + 1 + ] + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + Array.isArray(value) && value.length === 3 + && value[0] === 1 + && !!value[1] && typeof value[1] === "object" && !Array.isArray(value[1]) + && value[1].z === 2 + && !!value[2] && typeof value[2] === "object" && !Array.isArray(value[2]) + && value[2].a === 3 + && value[2].b === 3 + && Array.isArray(value[2].c) && value[2].c.length === 2 + && value[2].c[0] === 5 + && value[2].c[1] === 6 + ) + }" + `) + + vi.expect(jit( + t.eq( + [ + 1, + { z: 2 }, + { + a: 3, + b: 3, + c: [5, 6], + } + ] + ))).toMatchInlineSnapshot + (` + "function check(value) { + return ( + Array.isArray(value) && value.length === 3 + && value[0] === 1 + && !!value[1] && typeof value[1] === "object" && !Array.isArray(value[1]) + && value[1].z === 2 + && !!value[2] && typeof value[2] === "object" && !Array.isArray(value[2]) + && value[2].a === 3 + && value[2].b === 3 + && Array.isArray(value[2].c) && value[2].c.length === 2 + && value[2].c[0] === 5 + && value[2].c[1] === 6 + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.optional(...)', () => { + vi.expect(jit( + t.optional(t.eq(1)) + )).toMatchInlineSnapshot + (` + "function check(value) { + return value === undefined || value === 1 + }" + `) + + vi.expect(jit( + t.optional(t.optional(t.eq(1))) + )).toMatchInlineSnapshot + (` + "function check(value) { + return value === undefined || (value === undefined || value === 1) + }" + `) + + vi.expect(jit( + t.optional( + t.union( + t.eq(1000), + t.eq(2000), + t.eq(3000), + t.eq(4000), + t.eq(5000), + t.eq(6000), + ) + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + value === undefined + || (value === 1000 || value === 2000 || value === 3000 || value === 4000 || value === 5000 || value === 6000) + ) + }" + `) + + vi.expect(jit( + t.optional( + t.union( + t.eq(1000), + t.eq(2000), + t.eq(3000), + t.eq(4000), + t.eq(5000), + t.eq(6000), + t.eq(9000), + ) + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + value === undefined + || ( + value === 1000 + || value === 2000 + || value === 3000 + || value === 4000 + || value === 5000 + || value === 6000 + || value === 9000 + ) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.array(...)', () => { + vi.expect(jit( + t.array(t.eq(1)) + )).toMatchInlineSnapshot + (` + "function check(value) { + return Array.isArray(value) && value.every((value) => value === 1) + }" + `) + + vi.expect(jit( + t.array(t.array(t.eq(2))) + )).toMatchInlineSnapshot + (` + "function check(value) { + return Array.isArray(value) && value.every((value) => Array.isArray(value) && value.every((value) => value === 2)) + }" + `) + + vi.expect(jit( + t.array(t.array(t.array(t.array(t.array(t.array(t.eq(3))))))) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + Array.isArray(value) && value.every((value) => + Array.isArray(value) && value.every((value) => + Array.isArray(value) && value.every((value) => + Array.isArray(value) && value.every((value) => + Array.isArray(value) && value.every((value) => Array.isArray(value) && value.every((value) => value === 3)) + ) + ) + ) + ) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: t.record(...)', () => { + vi.expect(jit( + t.record(t.eq(1)) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) + && !(value instanceof Date) && !(value instanceof Uint8Array) + && Object.entries(value).every(([key, value]) => + typeof key === "string" && value === 1 + ) + ) + }" + `) + + vi.expect(jit( + t.record(t.record(t.eq(2))) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) + && !(value instanceof Date) && !(value instanceof Uint8Array) + && Object.entries(value).every(([key, value]) => + typeof key === "string" && !!value && typeof value === "object" && !Array.isArray(value) + && !(value instanceof Date) && !(value instanceof Uint8Array) + && Object.entries(value).every(([key, value]) => + typeof key === "string" && value === 2 + ) + ) + ) + }" + `) + + }) + + vi.it.only('〖⛳️〗› ❲jit❳: t.union(...)', () => { + + vi.expect(jit( + t.union() + )).toMatchInlineSnapshot + (` + "function check(value) { + return false + }" + `) + + vi.expect(jit( + t.union(t.never) + )).toMatchInlineSnapshot + (` + "function check(value) { + return (false) + }" + `) + + vi.expect(jit( + t.union(t.unknown) + )).toMatchInlineSnapshot + (` + "function check(value) { + return (true) + }" + `) + + vi.expect(jit( + t.union(t.union()) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ((false)) + }" + `) + + vi.expect(jit( + t.union( + t.integer, + t.bigint, + ) + )).toMatchInlineSnapshot + + (` + "function check(value) { + return (Number.isSafeInteger(value)) || (typeof value === "bigint") + }" + `) + + vi.expect(jit( + t.union( + t.boolean, + t.symbol, + t.integer, + t.bigint, + t.number, + t.string, + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (typeof value === "symbol") + || (typeof value === "boolean") + || (Number.isSafeInteger(value)) + || (typeof value === "bigint") + || (Number.isFinite(value)) + || (typeof value === "string") + ) + }" + `) + + vi.expect(jit( + t.union( + t.object({ + a: t.eq(1), + }), + t.object({ + b: t.eq(2), + }), + t.object({ + c: t.eq(3) + }) + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (!!value && typeof value === "object" && !Array.isArray(value) && value.a === 1) + || (!!value && typeof value === "object" && !Array.isArray(value) && value.b === 2) + || (!!value && typeof value === "object" && !Array.isArray(value) && value.c === 3) + ) + }" + `) + + vi.expect(jit( + t.union( + t.eq(9000), + t.union( + t.object({ + a: t.eq(1), + }), + t.object({ + b: t.eq(2), + }), + t.object({ + c: t.eq(3) + }) + ) + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + (value === 9000) + || (( + (!!value && typeof value === "object" && !Array.isArray(value) && value.a === 1) + || (!!value && typeof value === "object" && !Array.isArray(value) && value.b === 2) + || (!!value && typeof value === "object" && !Array.isArray(value) && value.c === 3) + )) + ) + }" + `) + + }) + + vi.it('〖⛳️〗› ❲jit❳: t.intersect(...)', () => { + vi.expect(jit( + t.intersect(t.unknown) + )).toMatchInlineSnapshot + (` + "function check(value) { + return true + }" + `) + + vi.expect(jit( + t.intersect( + t.object({ + a: t.eq(1), + }), + t.object({ + b: t.eq(2), + }), + t.object({ + c: t.eq(3) + }) + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) && value.a === 1 + && !!value && typeof value === "object" && !Array.isArray(value) && value.b === 2 + && !!value && typeof value === "object" && !Array.isArray(value) && value.c === 3 + ) + }" + `) + + vi.expect(jit( + t.intersect( + t.eq(9000), + t.intersect( + t.object({ + a: t.eq(1), + }), + t.object({ + b: t.eq(2), + }), + t.object({ + c: t.eq(3) + }), + ) + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + value === 9000 + && (!!value && typeof value === "object" && !Array.isArray(value) && value.a === 1 + && !!value && typeof value === "object" && !Array.isArray(value) && value.b === 2 + && !!value && typeof value === "object" && !Array.isArray(value) && value.c === 3) + ) + }" + `) + + }) + + vi.it('〖⛳️〗› ❲jit❳: t.tuple(...)', () => { + vi.expect(jit( + t.tuple() + )).toMatchInlineSnapshot + (` + "function check(value) { + return Array.isArray(value) && value.length === 0 + }" + `) + + vi.expect(jit( + t.tuple(t.unknown) + )).toMatchInlineSnapshot + (` + "function check(value) { + return Array.isArray(value) && value.length === 1 && true + }" + `) + + vi.expect(jit( + t.tuple( + t.tuple(), + t.tuple(), + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + Array.isArray(value) && value.length === 2 + && Array.isArray(value[0]) && value[0].length === 0 + && Array.isArray(value[1]) && value[1].length === 0 + ) + }" + `) + + vi.expect(jit( + t.tuple( + t.tuple( + t.eq('[0][0]'), + ), + t.tuple( + t.eq('[1][0]'), + ), + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + Array.isArray(value) && value.length === 2 + && Array.isArray(value[0]) && value[0].length === 1 && value[0][0] === "[0][0]" + && Array.isArray(value[1]) && value[1].length === 1 && value[1][0] === "[1][0]" + ) + }" + `) + + vi.expect(jit( + t.tuple( + t.tuple( + t.eq('[0][0]'), + t.eq('[0][1]'), + ), + t.tuple( + t.eq('[1][0]'), + t.eq('[1][1]'), + ) + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + Array.isArray(value) && value.length === 2 + && Array.isArray(value[0]) && value[0].length === 2 && value[0][0] === "[0][0]" && value[0][1] === "[0][1]" + && Array.isArray(value[1]) && value[1].length === 2 && value[1][0] === "[1][0]" && value[1][1] === "[1][1]" + ) + }" + `) + + vi.expect(jit( + t.tuple( + t.tuple( + t.tuple( + t.eq('[0][0][0]'), + t.eq('[0][0][1]'), + t.eq('[0][0][2]'), + ), + t.tuple( + t.eq('[0][1][0]'), + t.eq('[0][1][1]'), + t.eq('[0][1][2]'), + ), + t.tuple( + t.eq('[0][2][0]'), + t.eq('[0][2][1]'), + t.eq('[0][2][2]'), + ), + ), + t.tuple( + t.tuple( + t.eq('[1][0][0]'), + t.eq('[1][0][1]'), + t.eq('[1][0][2]'), + ), + t.tuple( + t.eq('[1][1][0]'), + t.eq('[1][1][1]'), + t.eq('[1][1][2]'), + ), + t.tuple( + t.eq('[1][2][0]'), + t.eq('[1][2][1]'), + t.eq('[1][2][2]'), + ), + ), + t.tuple( + t.tuple( + t.eq('[2][0][0]'), + t.eq('[2][0][1]'), + t.eq('[2][0][2]'), + ), + t.tuple( + t.eq('[2][1][0]'), + t.eq('[2][1][1]'), + t.eq('[2][1][2]'), + ), + t.tuple( + t.eq('[2][2][0]'), + t.eq('[2][2][1]'), + t.eq('[2][2][2]'), + ), + ) + ) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + Array.isArray(value) && value.length === 3 + && Array.isArray(value[0]) && value[0].length === 3 + && Array.isArray(value[0][0]) && value[0][0].length === 3 + && value[0][0][0] === "[0][0][0]" + && value[0][0][1] === "[0][0][1]" + && value[0][0][2] === "[0][0][2]" + && Array.isArray(value[0][1]) && value[0][1].length === 3 + && value[0][1][0] === "[0][1][0]" + && value[0][1][1] === "[0][1][1]" + && value[0][1][2] === "[0][1][2]" + && Array.isArray(value[0][2]) && value[0][2].length === 3 + && value[0][2][0] === "[0][2][0]" + && value[0][2][1] === "[0][2][1]" + && value[0][2][2] === "[0][2][2]" + && Array.isArray(value[1]) && value[1].length === 3 + && Array.isArray(value[1][0]) && value[1][0].length === 3 + && value[1][0][0] === "[1][0][0]" + && value[1][0][1] === "[1][0][1]" + && value[1][0][2] === "[1][0][2]" + && Array.isArray(value[1][1]) && value[1][1].length === 3 + && value[1][1][0] === "[1][1][0]" + && value[1][1][1] === "[1][1][1]" + && value[1][1][2] === "[1][1][2]" + && Array.isArray(value[1][2]) && value[1][2].length === 3 + && value[1][2][0] === "[1][2][0]" + && value[1][2][1] === "[1][2][1]" + && value[1][2][2] === "[1][2][2]" + && Array.isArray(value[2]) && value[2].length === 3 + && Array.isArray(value[2][0]) && value[2][0].length === 3 + && value[2][0][0] === "[2][0][0]" + && value[2][0][1] === "[2][0][1]" + && value[2][0][2] === "[2][0][2]" + && Array.isArray(value[2][1]) && value[2][1].length === 3 + && value[2][1][0] === "[2][1][0]" + && value[2][1][1] === "[2][1][1]" + && value[2][1][2] === "[2][1][2]" + && Array.isArray(value[2][2]) && value[2][2].length === 3 + && value[2][2][0] === "[2][2][0]" + && value[2][2][1] === "[2][2][1]" + && value[2][2][2] === "[2][2][2]" + ) + }" + `) + + }) + + vi.it.only('〖⛳️〗› ❲jit❳: object(...)', () => { + + vi.expect(jit( + t.object({}) + )).toMatchInlineSnapshot + (` + "function check(value) { + return !!value && typeof value === "object" && !Array.isArray(value) + }" + `) + + vi.expect(jit( + t.object({ + A: t.optional(t.number.min(1)), + }) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) + && (value.A === undefined || (Number.isFinite(value.A) && 1 <= value.A)) + ) + }" + `) + + vi.expect(jit( + t.object({ + A: t.optional(t.number), + B: t.array(t.integer) + }) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) + && (value.A === undefined || Number.isFinite(value.A)) + && Array.isArray(value.B) && value.B.every((value) => Number.isSafeInteger(value)) + ) + }" + `) + + vi.expect(jit( + t.object({ + B: t.array(t.integer), + A: t.object({ + C: t.optional(t.number) + }), + }) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) + && Array.isArray(value.B) && value.B.every((value) => Number.isSafeInteger(value)) + && !!value.A && typeof value.A === "object" && !Array.isArray(value.A) + && (value.A.C === undefined || Number.isFinite(value.A.C)) + ) + }" + `) + + vi.expect(jit( + t.object({ + A: t.union( + t.object({ + B: t.optional(t.eq(1)), + C: t.eq(2), + }), + t.object({ + D: t.optional(t.eq(3)), + E: t.eq(4), + }) + ) + }) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) + && ( + (!!value.A && typeof value.A === "object" && !Array.isArray(value.A) + && value.A.C === 2 + && (value.A.B === undefined || value.A.B === 1)) + || (!!value.A && typeof value.A === "object" && !Array.isArray(value.A) + && value.A.E === 4 + && (value.A.D === undefined || value.A.D === 3)) + ) + ) + }" + `) + + vi.expect(jit( + t.object({ + a: t.record(t.object({ + b: t.string, + c: t.tuple() + })) + }) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) + && !!value.a && typeof value.a === "object" && !Array.isArray(value.a) + && !(value.a instanceof Date) && !(value.a instanceof Uint8Array) + && Object.entries(value.a).every(([key, value]) => + typeof key === "string" && !!value && typeof value === "object" && !Array.isArray(value) + && typeof value.b === "string" + && Array.isArray(value.c) && value.c.length === 0 + ) + ) + }" + `) + + vi.expect(jit( + t.object({ + F: t.union( + t.object({ F: t.number }), + t.object({ G: t.any }) + ), + }) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) + && ( + (!!value.F && typeof value.F === "object" && !Array.isArray(value.F) && true) + || (!!value.F && typeof value.F === "object" && !Array.isArray(value.F) && Number.isFinite(value.F.F)) + ) + ) + }" + `) + + vi.expect(jit( + t.object({ + "#1C": t.object({ + twoC: t.intersect( + t.object({ + '\\3A': t.optional(t.symbol), + '\\3B': t.optional( + t.array( + t.union( + t.eq({ tag: 'left' }), + t.eq({ tag: 'right' }), + ) + ) + ), + }), + t.object({ + g: t.tuple( + t.object({ h: t.any }), + ), + h: t.optional( + t.object({ + i: t.optional(t.boolean), + j: t.union( + t.number.moreThan(0).max(128), + t.bigint, + ), + }) + ), + }) + ), + twoB: t.eq({ + "#3B": [ + 1, + [2], + [[3]], + ], + "#3A": { + n: 'over 9000', + o: [ + { p: false }, + ], + } + }), + twoA: t.integer.between(-10, 10), + }), + "#1A": t.integer.min(3), + "#1B": t.tuple( + t.record(t.any), + ), + }) + )).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === "object" && !Array.isArray(value) + && (Number.isSafeInteger(value["#1A"]) && 3 <= value["#1A"]) + && Array.isArray(value["#1B"]) && value["#1B"].length === 1 + && !!value["#1B"][0] && typeof value["#1B"][0] === "object" && !Array.isArray(value["#1B"][0]) + && !(value["#1B"][0] instanceof Date) && !(value["#1B"][0] instanceof Uint8Array) + && Object.entries(value["#1B"][0]).every(([key, value]) => + typeof key === "string" && true + ) + && !!value["#1C"] && typeof value["#1C"] === "object" && !Array.isArray(value["#1C"]) + && (Number.isSafeInteger(value["#1C"].twoA) && -10 <= value["#1C"].twoA && value["#1C"].twoA <= 10) + && !!value["#1C"].twoB && typeof value["#1C"].twoB === "object" && !Array.isArray(value["#1C"].twoB) + && !!value["#1C"].twoB["#3A"] && typeof value["#1C"].twoB["#3A"] === "object" && !Array.isArray(value["#1C"].twoB["#3A"]) + && value["#1C"].twoB["#3A"].n === "over 9000" + && Array.isArray(value["#1C"].twoB["#3A"].o) && value["#1C"].twoB["#3A"].o.length === 1 + && !!value["#1C"].twoB["#3A"].o[0] && typeof value["#1C"].twoB["#3A"].o[0] === "object" && !Array.isArray(value["#1C"].twoB["#3A"].o[0]) + && value["#1C"].twoB["#3A"].o[0].p === false + && Array.isArray(value["#1C"].twoB["#3B"]) && value["#1C"].twoB["#3B"].length === 3 + && value["#1C"].twoB["#3B"][0] === 1 + && Array.isArray(value["#1C"].twoB["#3B"][1]) && value["#1C"].twoB["#3B"][1].length === 1 + && value["#1C"].twoB["#3B"][1][0] === 2 + && Array.isArray(value["#1C"].twoB["#3B"][2]) && value["#1C"].twoB["#3B"][2].length === 1 + && Array.isArray(value["#1C"].twoB["#3B"][2][0]) && value["#1C"].twoB["#3B"][2][0].length === 1 + && value["#1C"].twoB["#3B"][2][0][0] === 3 + && ( + !!value["#1C"].twoC && typeof value["#1C"].twoC === "object" && !Array.isArray(value["#1C"].twoC) + && Array.isArray(value["#1C"].twoC.g) && value["#1C"].twoC.g.length === 1 + && !!value["#1C"].twoC.g[0] && typeof value["#1C"].twoC.g[0] === "object" && !Array.isArray(value["#1C"].twoC.g[0]) + && true + && ( + value["#1C"].twoC.h === undefined + || !!value["#1C"].twoC.h && typeof value["#1C"].twoC.h === "object" && !Array.isArray(value["#1C"].twoC.h) + && (value["#1C"].twoC.h?.i === undefined || typeof value["#1C"].twoC.h?.i === "boolean") + && ( + (typeof value["#1C"].twoC.h?.j === "bigint") + || ((Number.isFinite(value["#1C"].twoC.h?.j) && 0 < value["#1C"].twoC.h?.j && value["#1C"].twoC.h?.j <= 128)) + ) + ) + && !!value["#1C"].twoC && typeof value["#1C"].twoC === "object" && !Array.isArray(value["#1C"].twoC) + && (value["#1C"].twoC["\\\\3A"] === undefined || typeof value["#1C"].twoC["\\\\3A"] === "symbol") + && ( + value["#1C"].twoC["\\\\3B"] === undefined + || Array.isArray(value["#1C"].twoC["\\\\3B"]) && value["#1C"].twoC["\\\\3B"].every((value) => + ( + (!!value && typeof value === "object" && !Array.isArray(value) + && value.tag === "left") + || (!!value && typeof value === "object" && !Array.isArray(value) + && value.tag === "right") + ) + ) + ) + ) + ) + }" + `) + + }) + +}) + + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: configure', () => { + vi.it('〖⛳️〗› ❲jit❳: treatArraysAsObjects', () => { + let schema = t.object({ + F: t.union( + t.object({ F: t.number }), + t.object({ G: t.any }) + ), + }) + + configure({ + schema: { + treatArraysAsObjects: false, + } + }) && vi.expect(jit(schema)).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === 'object' && !Array.isArray(value) + && + !!value.F && typeof value.F === 'object' && !Array.isArray(value.F) + && true + || !!value.F && typeof value.F === 'object' && !Array.isArray(value.F) + && Number.isFinite(value.F.F) + ) + }" + `) + + configure({ + schema: { + treatArraysAsObjects: true, + } + }) && vi.expect(jit(schema)).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === 'object' + && + !!value.F && typeof value.F === 'object' + && true + || !!value.F && typeof value.F === 'object' + && Number.isFinite(value.F.F) + ) + }" + `) + }) + + vi.it('〖⛳️〗› ❲jit❳: exactOptional', () => { + let schema = t.object({ + a: t.number, + b: t.optional(t.string), + c: t.optional(t.number.min(8)), + }) + + configure({ + schema: { + optionalTreatment: 'exactOptional', + treatArraysAsObjects: false, + } + }) && vi.expect(jit(schema)).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === 'object' && !Array.isArray(value) + && Number.isFinite(value.a) + && (!Object.hasOwn(value, 'c') || (Number.isFinite(value.c) && 8 <= value.c)) + && (!Object.hasOwn(value, 'b') || typeof value.b === 'string') + ) + }" + `) + + configure({ + schema: { + optionalTreatment: 'presentButUndefinedIsOK', + treatArraysAsObjects: false, + } + }) && vi.expect(jit(schema)).toMatchInlineSnapshot + (` + "function check(value) { + return ( + !!value && typeof value === 'object' && !Array.isArray(value) + && Number.isFinite(value.a) + && value.c === undefined || (Number.isFinite(value.c) && 8 <= value.c) + && (value.b === undefined || typeof value.b === 'string') + ) + }" + `) + }) +}) + diff --git a/packages/schema-jit-compiler/test/json.test.ts b/packages/schema-jit-compiler/test/json.test.ts new file mode 100644 index 00000000..27e7cbd5 --- /dev/null +++ b/packages/schema-jit-compiler/test/json.test.ts @@ -0,0 +1,167 @@ +import * as vi from 'vitest' +import { fc, test } from '@fast-check/vitest' + +import { t } from '@traversable/schema-core' +import type * as T from '@traversable/registry' +import { fn, URI, symbol } from '@traversable/registry' +import { Seed } from '@traversable/schema-seed' +import type { Algebra, Index } from '@traversable/schema-jit-compiler' +import { Json } from '@traversable/schema-jit-compiler' +import { getJsonWeight as getWeight, sortJson as sort } from '@traversable/schema-jit-compiler' + + + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () => { + vi.it('〖⛳️〗› ❲Json.unfoldComparator❳', () => { + vi.expect(getWeight(null)).toMatchInlineSnapshot(`2`) + vi.expect(getWeight(undefined)).toMatchInlineSnapshot(`1`) + vi.expect(getWeight(false)).toMatchInlineSnapshot(`4`) + vi.expect(getWeight(true)).toMatchInlineSnapshot(`4`) + vi.expect(getWeight([true, false, ['heyy']])).toMatchInlineSnapshot(`280`) + + vi.expect(sort([1, { a: 3, b: 3, c: [5, 6] }, { z: 2 }])).toMatchInlineSnapshot(` + { + "def": [ + { + "def": 1, + "tag": "@traversable/schema-core/URI::bottom", + }, + { + "def": [ + [ + "z", + { + "def": 2, + "tag": "@traversable/schema-core/URI::bottom", + }, + ], + ], + "tag": "@traversable/schema-core/URI::object", + }, + { + "def": [ + [ + "a", + { + "def": 3, + "tag": "@traversable/schema-core/URI::bottom", + }, + ], + [ + "b", + { + "def": 3, + "tag": "@traversable/schema-core/URI::bottom", + }, + ], + [ + "c", + { + "def": [ + { + "def": 5, + "tag": "@traversable/schema-core/URI::bottom", + }, + { + "def": 6, + "tag": "@traversable/schema-core/URI::bottom", + }, + ], + "tag": "@traversable/schema-core/URI::array", + }, + ], + ], + "tag": "@traversable/schema-core/URI::object", + }, + ], + "tag": "@traversable/schema-core/URI::array", + } + `) + + vi.expect(sort([{ a: 2 }, { a: true }])).toMatchInlineSnapshot(` + { + "def": [ + { + "def": [ + [ + "a", + { + "def": true, + "tag": "@traversable/schema-core/URI::bottom", + }, + ], + ], + "tag": "@traversable/schema-core/URI::object", + }, + { + "def": [ + [ + "a", + { + "def": 2, + "tag": "@traversable/schema-core/URI::bottom", + }, + ], + ], + "tag": "@traversable/schema-core/URI::object", + }, + ], + "tag": "@traversable/schema-core/URI::array", + } + `) + + vi.expect(sort([{ a: [[10]] }, { a: [[false]] }])).toMatchInlineSnapshot(` + { + "def": [ + { + "def": [ + [ + "a", + { + "def": [ + { + "def": [ + { + "def": false, + "tag": "@traversable/schema-core/URI::bottom", + }, + ], + "tag": "@traversable/schema-core/URI::array", + }, + ], + "tag": "@traversable/schema-core/URI::array", + }, + ], + ], + "tag": "@traversable/schema-core/URI::object", + }, + { + "def": [ + [ + "a", + { + "def": [ + { + "def": [ + { + "def": 10, + "tag": "@traversable/schema-core/URI::bottom", + }, + ], + "tag": "@traversable/schema-core/URI::array", + }, + ], + "tag": "@traversable/schema-core/URI::array", + }, + ], + ], + "tag": "@traversable/schema-core/URI::object", + }, + ], + "tag": "@traversable/schema-core/URI::array", + } + `) + + + }) +}) diff --git a/packages/schema-jit-compiler/test/sort.test.ts b/packages/schema-jit-compiler/test/sort.test.ts new file mode 100644 index 00000000..47186105 --- /dev/null +++ b/packages/schema-jit-compiler/test/sort.test.ts @@ -0,0 +1,61 @@ +import * as vi from 'vitest' +import { t } from '@traversable/schema-core' + +import { jit, WeightByTypeName } from '@traversable/schema-jit-compiler' + +let SHALLOW_ORDER = { + [WeightByTypeName.never]: t.never, + [WeightByTypeName.any]: t.any, + [WeightByTypeName.unknown]: t.unknown, + [WeightByTypeName.void]: t.void, + [WeightByTypeName.null]: t.null, + [WeightByTypeName.undefined]: t.undefined, + [WeightByTypeName.symbol]: t.symbol, + [WeightByTypeName.boolean]: t.boolean, + [WeightByTypeName.integer]: t.integer, + [WeightByTypeName.bigint]: t.bigint, + [WeightByTypeName.number]: t.number, + [WeightByTypeName.string]: t.string, + [WeightByTypeName.eq]: t.eq({}), + [WeightByTypeName.optional]: t.optional(t.never), + [WeightByTypeName.array]: t.array(t.never), + [WeightByTypeName.record]: t.record(t.never), + [WeightByTypeName.intersect]: t.intersect(), + [WeightByTypeName.union]: t.union(), + [WeightByTypeName.tuple]: t.tuple(), + [WeightByTypeName.object]: t.object({}), +} + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () => { + vi.it('〖⛳️〗› ❲sort❳: shallow sort order is correct', () => { + vi.expect(jit(t.object(SHALLOW_ORDER))).toMatchInlineSnapshot(` + "function check(value) { + return ( + !!value && typeof value === 'object' && !Array.isArray(value) + && false + && true + && true + && value["30"] === void 0 + && value["40"] === undefined + && value["50"] === null + && typeof value["60"] === 'symbol' + && typeof value["70"] === 'boolean' + && Number.isSafeInteger(value["80"]) + && typeof value["90"] === 'bigint' + && Number.isFinite(value["100"]) + && typeof value["110"] === 'string' + && (value["120"] === undefined || false) + && true + && false + && Array.isArray(value["150"]) && value["150"].length === 0 + && !!value["160"] && typeof value["160"] === 'object' && !Array.isArray(value["160"]) + && Array.isArray(value["170"]) && value["170"].every((value) => false) + && !!value["180"] && typeof value["180"] === 'object' && !Array.isArray(value["180"]) + && !(value["180"] instanceof Date) && !(value["180"] instanceof Uint8Array) + && Object.entries(value["180"]).every(([key, value]) => typeof key === 'string' ? false : true) + && !!value["190"] && typeof value["190"] === 'object' && !Array.isArray(value["190"]) + ) + }" + `) + }) +}) diff --git a/packages/schema-jit-compiler/test/version.test.ts b/packages/schema-jit-compiler/test/version.test.ts new file mode 100644 index 00000000..ef4ebea5 --- /dev/null +++ b/packages/schema-jit-compiler/test/version.test.ts @@ -0,0 +1,10 @@ +import * as vi from 'vitest' +import pkg from '../package.json' with { type: 'json' } +import { VERSION } from '@traversable/schema-jit-compiler' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () => { + vi.it('〖⛳️〗› ❲schemaJitCompiler#VERSION❳', () => { + const expected = `${pkg.name}@${pkg.version}` + vi.assert.equal(VERSION, expected) + }) +}) diff --git a/packages/schema-jit-compiler/tsconfig.build.json b/packages/schema-jit-compiler/tsconfig.build.json new file mode 100644 index 00000000..18db6e09 --- /dev/null +++ b/packages/schema-jit-compiler/tsconfig.build.json @@ -0,0 +1,15 @@ +{ + "extends": "./tsconfig.src.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", + "types": ["node"], + "declarationDir": "build/dts", + "outDir": "build/esm", + "stripInternal": true + }, + "references": [ + { "path": "../registry" }, + { "path": "../schema-core" }, + { "path": "../schema-seed" } + ] +} diff --git a/packages/schema-jit-compiler/tsconfig.json b/packages/schema-jit-compiler/tsconfig.json new file mode 100644 index 00000000..2c291d21 --- /dev/null +++ b/packages/schema-jit-compiler/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [], + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "tsconfig.test.json" } + ] +} diff --git a/packages/schema-jit-compiler/tsconfig.src.json b/packages/schema-jit-compiler/tsconfig.src.json new file mode 100644 index 00000000..6042e159 --- /dev/null +++ b/packages/schema-jit-compiler/tsconfig.src.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", + "rootDir": "src", + "types": ["node"], + "outDir": "build/src" + }, + "references": [ + { "path": "../registry" }, + { "path": "../schema-core" }, + { "path": "../schema-seed" } + ], + "include": ["src"] +} diff --git a/packages/schema-jit-compiler/tsconfig.test.json b/packages/schema-jit-compiler/tsconfig.test.json new file mode 100644 index 00000000..3ad0d988 --- /dev/null +++ b/packages/schema-jit-compiler/tsconfig.test.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", + "rootDir": "test", + "types": ["node"], + "noEmit": true + }, + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "../registry" }, + { "path": "../schema-core" }, + { "path": "../schema-seed" } + ], + "include": ["test"] +} diff --git a/packages/schema-jit-compiler/vite.config.ts b/packages/schema-jit-compiler/vite.config.ts new file mode 100644 index 00000000..64dba4ad --- /dev/null +++ b/packages/schema-jit-compiler/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import sharedConfig from '../../vite.config.js' + +const localConfig = defineConfig({}) + +export default mergeConfig(sharedConfig, localConfig) \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6c2dd474..850d855a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -314,6 +314,19 @@ importers: version: 1.1.1 publishDirectory: dist + packages/schema-jit-compiler: + devDependencies: + '@traversable/registry': + specifier: workspace:^ + version: link:../registry/dist + '@traversable/schema-core': + specifier: workspace:^ + version: link:../schema-core/dist + '@traversable/schema-seed': + specifier: workspace:^ + version: link:../schema-seed/dist + publishDirectory: dist + packages/schema-seed: devDependencies: '@traversable/json': diff --git a/tsconfig.base.json b/tsconfig.base.json index 94d2a9a1..0569aabf 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -49,6 +49,12 @@ "packages/schema-generator/src/index.js" ], "@traversable/schema-generator/*": ["packages/schema-generator/*.js"], + "@traversable/schema-jit-compiler": [ + "packages/schema-jit-compiler/src/index.js" + ], + "@traversable/schema-jit-compiler/*": [ + "packages/schema-jit-compiler/*.js" + ], "@traversable/schema-seed": ["packages/schema-seed/src/index.js"], "@traversable/schema-seed/*": ["packages/schema-seed/src/*.js"], "@traversable/schema-to-json-schema": [ diff --git a/tsconfig.build.json b/tsconfig.build.json index 7e6a2ea8..2793087a 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -9,6 +9,7 @@ { "path": "packages/registry/tsconfig.build.json" }, { "path": "packages/schema-core/tsconfig.build.json" }, { "path": "packages/schema-generator/tsconfig.build.json" }, + { "path": "packages/schema-jit-compiler/tsconfig.build.json" }, { "path": "packages/schema-seed/tsconfig.build.json" }, { "path": "packages/schema-to-json-schema/tsconfig.build.json" }, { "path": "packages/schema-to-string/tsconfig.build.json" }, diff --git a/tsconfig.json b/tsconfig.json index 8976eff4..32a98ca4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ { "path": "packages/schema" }, { "path": "packages/schema-core" }, { "path": "packages/schema-generator" }, + { "path": "packages/schema-jit-compiler" }, { "path": "packages/schema-seed" }, { "path": "packages/schema-to-json-schema" }, { "path": "packages/schema-to-string" }, From d747501cb7db78476a4e41b8df17e5a0c37e5fd0 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 16 Apr 2025 14:53:18 -0500 Subject: [PATCH 37/45] fix(jit): formatting oversight --- packages/schema-jit-compiler/src/jit.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/schema-jit-compiler/src/jit.ts b/packages/schema-jit-compiler/src/jit.ts index ae9f8a0f..762f620a 100644 --- a/packages/schema-jit-compiler/src/jit.ts +++ b/packages/schema-jit-compiler/src/jit.ts @@ -167,7 +167,7 @@ export namespace Jit { let SINGLE_LINE = WIDTH < MAX_WIDTH let OPEN = SINGLE_LINE ? '(' : ('(' + indent(2)) let CLOSE = SINGLE_LINE ? ')' : (indent(0) + ')') - let BODY = SINGLE_LINE ? (CHECK + ' || ' + x.def) : (CHECK + indent(0) + '|| ' + x.def) + let BODY = SINGLE_LINE ? (CHECK + ' || ' + x.def) : (CHECK + indent(2) + '|| ' + x.def) return '' + OPEN + BODY @@ -237,8 +237,8 @@ export namespace Jit { // let OPEN = CHILD_COUNT < 2 ? '' : '(' // let CLOSE = CHILD_COUNT < 2 ? '' : ')' - let OPEN = SINGLE_LINE ? '' : ('(' + indent(2)) - let CLOSE = SINGLE_LINE ? '' : (indent(0) + ')') + let OPEN = SINGLE_LINE || ix.isRoot ? '(' : ('(' + indent(2)) + let CLOSE = SINGLE_LINE || ix.isRoot ? ')' : (indent(0) + ')') let BODY = CHILD_COUNT === 0 ? 'true' : SINGLE_LINE ? x.def.join(' && ') From 21c023cb868e26bd4ae8b1c81fc46cc851724617 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Wed, 16 Apr 2025 18:30:23 -0500 Subject: [PATCH 38/45] feat(jit): finished testing --- packages/schema-jit-compiler/src/functor.ts | 14 +- packages/schema-jit-compiler/src/jit.ts | 73 +- packages/schema-jit-compiler/src/sort.ts | 53 +- packages/schema-jit-compiler/test/jit.test.ts | 640 +++++++++--------- .../schema-jit-compiler/test/sort.test.ts | 24 +- 5 files changed, 407 insertions(+), 397 deletions(-) diff --git a/packages/schema-jit-compiler/src/functor.ts b/packages/schema-jit-compiler/src/functor.ts index c54ab8d1..6506e8fc 100644 --- a/packages/schema-jit-compiler/src/functor.ts +++ b/packages/schema-jit-compiler/src/functor.ts @@ -37,11 +37,15 @@ export function keyAccessor(key: keyof any | undefined, $: Index) { : '' } -export function indexAccessor(index: keyof any | undefined, $: { isOptional?: boolean }) { - return typeof index === 'number' ? $.isOptional - ? `?.[${index}]` - : `[${index}]` - : '' +// Reading `x` to access the "preSortIndex" is a hack to make sure +// we preserve the original order of the tuple, even while sorting +export function indexAccessor(index: keyof any | undefined, $: { isOptional?: boolean }, x?: any) { + return 'preSortIndex' in x + ? $.isOptional ? `?.[${x.preSortIndex}]` : `[${x.preSortIndex}]` + : typeof index === 'number' ? $.isOptional + ? `?.[${index}]` + : `[${index}]` + : '' } export let defaultIndex: Index = { diff --git a/packages/schema-jit-compiler/src/jit.ts b/packages/schema-jit-compiler/src/jit.ts index 762f620a..4bb293ea 100644 --- a/packages/schema-jit-compiler/src/jit.ts +++ b/packages/schema-jit-compiler/src/jit.ts @@ -124,8 +124,12 @@ export namespace Jit { case x.tag === URI.number: { let CHECK = `Number.isFinite(${VAR})` - let MIN_CHECK = t.number(x.exclusiveMinimum) ? ` && ${x.exclusiveMinimum} < ${VAR}` : t.number(x.minimum) ? ` && ${x.minimum} <= ${VAR}` : '' - let MAX_CHECK = t.number(x.exclusiveMaximum) ? ` && ${VAR} < ${x.exclusiveMaximum}` : t.number(x.maximum) ? ` && ${VAR} <= ${x.maximum}` : '' + let MIN_CHECK = t.number(x.exclusiveMinimum) + ? ` && ${x.exclusiveMinimum} < ${VAR}` + : t.number(x.minimum) ? ` && ${x.minimum} <= ${VAR}` : '' + let MAX_CHECK = t.number(x.exclusiveMaximum) + ? ` && ${VAR} < ${x.exclusiveMaximum}` + : t.number(x.maximum) ? ` && ${VAR} <= ${x.maximum}` : '' let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' return '' @@ -184,8 +188,6 @@ export namespace Jit { let SINGLE_LINE = WIDTH < MAX_WIDTH let OPEN = SINGLE_LINE ? '' : indent(4) let CLOSE = SINGLE_LINE ? ')' : (indent(2) + ')') - // let OUTER_OPEN = SINGLE_LINE ? '' : indent(2) - // let OUTER_CLOSE = SINGLE_LINE ? '' : indent(0) return '' + OUTER_CHECK + INNER_CHECK @@ -231,22 +233,9 @@ export namespace Jit { let CHILD_COUNT = x.def.length let WIDTH = ix.offset + x.def.join(' || ').length let SINGLE_LINE = WIDTH < MAX_WIDTH - - // let OPEN = CHILD_COUNT < 2 ? '' : SINGLE_LINE ? '(' : ('(' + indent(2)) - // let CLOSE = CHILD_COUNT < 2 ? '' : SINGLE_LINE ? ')' : (indent(0) + ')') - // let OPEN = CHILD_COUNT < 2 ? '' : '(' - // let CLOSE = CHILD_COUNT < 2 ? '' : ')' - let OPEN = SINGLE_LINE || ix.isRoot ? '(' : ('(' + indent(2)) let CLOSE = SINGLE_LINE || ix.isRoot ? ')' : (indent(0) + ')') - - let BODY = CHILD_COUNT === 0 ? 'true' - : SINGLE_LINE ? x.def.join(' && ') - : x.def.join(indent(2) + '&& ') - - // : SINGLE_LINE ? x.def.join(' && ') - // : x.def.join(indent(2) + '&& ') - + let BODY = CHILD_COUNT === 0 ? 'true' : SINGLE_LINE ? x.def.join(' && ') : x.def.join(indent(2) + '&& ') return '' + OPEN + BODY @@ -258,16 +247,9 @@ export namespace Jit { let CHECK = `Array.isArray(${VAR}) && ${VAR}.length === ${CHILD_COUNT}` let WIDTH = ix.offset + CHECK.length + x.def.join(' && ').length let SINGLE_LINE = WIDTH < MAX_WIDTH - // let OPEN = SINGLE_LINE ? '' : ('(' + indent(2)) - // let CLOSE = SINGLE_LINE ? '' : (indent(0) + ')') - let JOIN = SINGLE_LINE ? '' : indent(2) let CHILDREN = CHILD_COUNT === 0 ? '' : x.def.map((v) => JOIN + (SINGLE_LINE ? ' && ' : '&& ') + v).join('') - return '' - // + OPEN - + CHECK - + CHILDREN - // + CLOSE + return CHECK + CHILDREN } case x.tag === URI.object: { @@ -280,36 +262,23 @@ export namespace Jit { : v ) let WIDTH = ix.offset + CHECK.length + CHILDREN.join(' && ').length - let SINGLE_LINE = WIDTH < MAX_WIDTH - // let OPEN = SINGLE_LINE ? '' : ('(' + indent(2)) - // let CLOSE = SINGLE_LINE ? '' : (indent(0) + ')') let JOIN = SINGLE_LINE ? '' : indent(2) let BODY = CHILD_COUNT === 0 ? '' : CHILDREN.map((v) => JOIN + (SINGLE_LINE ? ' && ' : '&& ') + v).join('') return '' - // + OPEN + CHECK + BODY - // + CLOSE - - // + `!!${VAR} && typeof ${VAR} === "object"${NON_ARRAY_CHECK}` - // + (x.def.length === 0 ? '' : JOIN + x.def.map( - // ([k, v]) => IS_EXACT_OPTIONAL && OPTIONAL_KEYS.includes(k) - // ? `(!Object.hasOwn(${VAR}, "${parseKey(k)}") || ${v})` - // : v - // ).join(JOIN)) } } } - - } export function jitJson(json: Json.Any, index?: Index): string export function jitJson(json: Json.Any, index?: Index) { return fn.pipe( - Json.sort(json), + json, + Json.sort, (sorted) => Json.fold(Jit.checkJson)(sorted, index).def, ) } @@ -319,7 +288,7 @@ export let check = fn.flow( Weighted.fold(Jit.check), ) -export let jit = (schema: t.Schema) => { +export let jitBody = (schema: t.Schema) => { let BODY = check(schema).trim() if (BODY.startsWith('(') && BODY.endsWith(')')) void (BODY = BODY.slice(1, -1)) @@ -328,23 +297,25 @@ export let jit = (schema: t.Schema) => { let OPEN = SINGLE_LINE ? '' : `(\r${' '.repeat(4)}` let CLOSE = SINGLE_LINE ? '' : `\r${' '.repeat(2)})` - return ` + return OPEN + BODY + CLOSE +} + + +export let jit = (schema: t.Schema) => ` function check(value) { - return ${OPEN}${BODY}${CLOSE} + return ${jitBody(schema)} } ` - .trim() -} + .trim() -export let jitParser = (schema: t.Schema) => { - let body = check(schema) +export let parseBody = (schema: t.Schema) => { return ` function parse(value) { function check(value) { return ( - ${body} + ${jitBody(schema)} ) } if (check(value)) return value @@ -356,7 +327,7 @@ function parse(value) { } export function compile(schema: S): ((x: S['_type'] | T.Unknown) => x is S['_type']) -export function compile(schema: t.Schema): Function { return globalThis.Function('value', 'return ' + check(schema)) } +export function compile(schema: t.Schema): Function { return globalThis.Function('value', 'return ' + jitBody(schema)) } export function compileParser(schema: S): ((x: S['_type'] | T.Unknown) => S['_type']) -export function compileParser(schema: t.Schema): Function { return globalThis.Function('value', 'return ' + check(schema)) } +export function compileParser(schema: t.Schema): Function { return globalThis.Function('value', 'return ' + parseBody(schema)) } diff --git a/packages/schema-jit-compiler/src/sort.ts b/packages/schema-jit-compiler/src/sort.ts index 19f0f1ef..162923fd 100644 --- a/packages/schema-jit-compiler/src/sort.ts +++ b/packages/schema-jit-compiler/src/sort.ts @@ -2,8 +2,7 @@ import type * as T from '@traversable/registry' import { fn, symbol, typeName, URI } from '@traversable/registry' import { t } from '@traversable/schema-core' -import type { Index } from './functor.js' -import { defaultIndex, keyAccessor, indexAccessor } from './functor.js' +import * as F from './functor.js' export let WeightByTypeName = { never: 0, @@ -29,7 +28,7 @@ export let WeightByTypeName = { } as const export interface Free extends T.HKT { [-1]: IR } -export type Algebra = T.IndexedAlgebra +export type Algebra = T.IndexedAlgebra export type IR = | t.Leaf @@ -65,7 +64,7 @@ export let map: T.Functor['map'] = (f) => { } } -export let Functor: T.Functor.Ix = { +export let Functor: T.Functor.Ix = { map, mapWithIndex(f) { return (xs, ix) => { @@ -119,15 +118,22 @@ export let Functor: T.Functor.Ix = { siblingCount: Math.max(xs.def.length - 1, 0), varName: ix.varName, }))) - case xs.tag === URI.tuple: return t.tuple.def(fn.map(xs.def, (x, i) => f(x, { - dataPath: [...ix.dataPath, i], - isOptional: ix.isOptional, - isRoot: false, - offset: ix.offset + 2, - schemaPath: [...ix.schemaPath, i], - siblingCount: Math.max(xs.def.length - 1, 0), - varName: ix.varName + indexAccessor(i, ix), - }))) + case xs.tag === URI.tuple: + return t.tuple.def(fn.map(xs.def, (x, i) => f(x, { + dataPath: [...ix.dataPath, i], + isOptional: ix.isOptional, + isRoot: false, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, i], + siblingCount: Math.max(xs.def.length - 1, 0), + /** + * Passing `x` to `indexAccessor` is a hack to make sure + * we preserve the original order of the tuple while we're + * applying a sorting optimization + */ + varName: ix.varName + F.indexAccessor(i, ix, x), + }))) + case xs.tag === URI.object: { return t.object.def( fn.map(xs.def, ([k, v]) => [k, f(v, { @@ -137,7 +143,7 @@ export let Functor: T.Functor.Ix = { offset: ix.offset + 2, schemaPath: [...ix.schemaPath, k], siblingCount: Math.max(Object.keys(xs.def).length - 1, 0), - varName: ix.varName + keyAccessor(k, ix), + varName: ix.varName + F.keyAccessor(k, ix), })] satisfies [any, any]), undefined, xs.opt, @@ -148,7 +154,7 @@ export let Functor: T.Functor.Ix = { }, } -export let fold = (algebra: Algebra) => (x: IR) => fn.cataIx(Functor)(algebra)(x, defaultIndex) +export let fold = (algebra: Algebra) => (x: IR) => fn.cataIx(Functor)(algebra)(x, F.defaultIndex) export let print = fold((x) => t.isNullary(x) ? x.tag : t.isBoundable(x) ? x.tag : x.def) export let toIR = t.fold( (x) => x.tag !== URI.object ? x : t.object.def( @@ -187,6 +193,18 @@ function getWeight(x: IR): number { } } +/** + * Binding the element's index to the element itself is a hack to make sure + * we preserve the original order of the tuple, even while sorting + */ +let bindPreSortIndices = (x: T[]) => { + for (let ix = 0, len = x.length; ix < len; ix++) { + let def = x[ix] + ; (def as any).preSortIndex = ix + } + return x +} + let sortAlgebra: Algebra = (x) => { switch (true) { default: return fn.exhaustive(x) @@ -198,7 +216,10 @@ let sortAlgebra: Algebra = (x) => { case x.tag === URI.record: return t.record.def(x.def) case x.tag === URI.union: return t.union.def(x.def.sort(weightComparator)) case x.tag === URI.intersect: return t.intersect.def([...x.def].sort(weightComparator)) - case x.tag === URI.tuple: return t.tuple.def(x.def.sort(weightComparator)) + case x.tag === URI.tuple: return ( + bindPreSortIndices(x.def), + t.tuple.def(x.def.sort(weightComparator)) + ) case x.tag === URI.object: return t.object.def( x.def.sort(([, l], [, r]) => weightComparator(l, r)), undefined, diff --git a/packages/schema-jit-compiler/test/jit.test.ts b/packages/schema-jit-compiler/test/jit.test.ts index 7e13c4de..84cf6e21 100644 --- a/packages/schema-jit-compiler/test/jit.test.ts +++ b/packages/schema-jit-compiler/test/jit.test.ts @@ -1,162 +1,258 @@ import * as vi from 'vitest' +import { fc, test } from '@fast-check/vitest' +import { Seed } from '@traversable/schema-seed' import { t, configure } from '@traversable/schema-core' import { compile, jit, jitJson } from '@traversable/schema-jit-compiler' -import { Seed } from '@traversable/schema-seed' -import { fc, test } from '@fast-check/vitest' import * as Arbitrary from './TODO.js' +vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: property tests (randomly generated inputs)', () => { -vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: property tests', () => { - - vi.describe.skip('〖⛳️〗‹‹ ❲compile❳: object', () => { - let schema = t.object({ - a: t.record(t.object({ - b: t.string, - c: t.tuple() - })) - }) - - let arbitrary = Arbitrary.fromSchema(schema) - // let check = compile(schema) - - test.prop([arbitrary], {})('〖⛳️〗› ❲compile❳: object', (data) => { - // vi.assert.isTrue(check(data)) - }) - - }) - - vi.it('〖⛳️〗› ❲jit❳', () => { - - let s = t.object({ - - "#1C": t.object({ - - twoC: t.intersect( - t.object({ - '\\3A': t.optional(t.symbol), - '\\3B': t.optional(t.array(t.union(t.eq({ tag: 'left' }), t.eq({ tag: 'right' })))), - }), - t.object({ - g: t.tuple( - t.object({ h: t.any }) - ), - // h: t.optional(t.object({ i: t.optional(t.boolean), j: t.union(t.number.moreThan(0).max(128), t.bigint) })), - }) - ), - - // twoB: t.eq({ - // "#3B": [ - // 1, - // [2], - // [[3]], - // ], - // "#3A": { - // n: 'over 9000', - // o: [ - // { p: false }, - // ], - // } - // }), - - // twoA: t.integer.between(-10, 10), - + let schema = t.object({ + "#1C": t.object({ + twoC: t.intersect( + t.object({ + '\\3A': t.optional(t.symbol), + '\\3B': t.optional(t.array(t.union(t.eq({ tag: 'left' }), t.eq({ tag: 'right' })))), + }), + t.object({ + g: t.tuple( + t.object({ h: t.any }) + ), + h: t.optional(t.object({ i: t.optional(t.boolean), j: t.union(t.number, t.bigint) })), + }) + ), + twoB: t.eq({ + "#3B": [ + 1, + [2], + [[3]], + ], + "#3A": { + n: 'over 9000', + o: [ + { p: false }, + ], + } }), + twoA: t.integer, + }), + "#1A": t.union(t.integer), + "#1B": t.tuple( + t.record(t.any), + ), + }) - // "#1A": t.union(t.integer.min(3)), - - // "#1B": t.tuple( - // t.record(t.any), - // ), - - }) - - let j = jit(s) + let check = compile(schema) + let arbitrary = Arbitrary.fromSchema(schema) + test.prop([arbitrary], { + // numRuns: 10_000, + endOnFailure: true, + })('〖⛳️〗› ❲jit.check❳: succeeds with randomly generated, valid input', (data) => { try { - let c = compile(s) - let a = Arbitrary.fromSchema(s) - let m = fc.sample(a, 1)[0] + vi.assert.isTrue(check(data)) + } catch (e) { + let jitted = jit(schema) - console.log(JSON.stringify(m, null, 2)) - console.log('test', c(m)) + console.error() + console.error('Check for valid, randomly generated data failed') + console.error('Schema:\r\n\n' + jitted + '\r\n\n') + console.error('Input:\r\n\n' + JSON.stringify(data, null, 2) + '\r\n\n') - } catch (e) { - vi.assert.fail('' - + '\r\n\n' - + ( - t.has('message', t.string)(e) - ? e.message - : JSON.stringify(e, null, 2) - ) - + '\r\n\n' - + '\t' - + 'Function body:' - + '\r\n\n' - + j - + '\r\n' - ) + vi.assert.fail(t.has('message', t.string)(e) ? e.message : JSON.stringify(e, null, 2)) } }) }) +vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: property tests (random generated schemas)', () => { + let seed = fc.letrec(Seed.seed()) + test.prop([seed.tree], { + endOnFailure: true, + numRuns: 10, + // numRuns: 10_000, + })( + 'property tests (random generated schemas)', (seed) => { + let schema = Seed.toSchema(seed) + let check = compile(schema) + let arbitrary = Arbitrary.fromSchema(schema) + let inputs = fc.sample(arbitrary, 100) + + for (let input of inputs) { + try { + vi.assert.isTrue(check(input)) + } catch (e) { + console.error(t.has('message', t.string)(e) ? e.message : JSON.stringify(e, null, 2)) + + } + } -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: compile(...)', () => { + } + ) +}) +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: compile(...)', () => { + vi.describe('〖⛳️〗‹‹ ❲compile❳: eq', () => { + let check = compile( + t.eq({ + a: false, + }) + ) + + vi.test.concurrent.for([ + /* FAILURE */ + {}, + { a: true }, + ])('t.eq check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + + vi.test.concurrent.for([ + /* SUCCESS */ + { a: false }, + ])('t.eq check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + }) - // vi.it('〖⛳️〗› ❲compile❳: object', () => { + vi.describe('〖⛳️〗‹‹ ❲compile❳: array', () => { + let check = compile( + t.array(t.boolean) + ) + + vi.test.concurrent.for([ + /* FAILURE */ + [1], + ])('t.array check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + + vi.test.concurrent.for([ + /* SUCCESS */ + [], + [Math.random() > 0.5], + ])('t.array check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + }) + vi.describe('〖⛳️〗‹‹ ❲compile❳: record', () => { + let check = compile( + t.record(t.boolean) + ) + + vi.test.concurrent.for([ + /* FAILURE */ + [], + { a: 0 }, + ])('t.record check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + + vi.test.concurrent.for([ + /* SUCCESS */ + {}, + { a: false }, + ])('t.record check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + }) - // // vi.assert.isTrue( - // // check({ a: {} }) - // // ) + vi.describe('〖⛳️〗‹‹ ❲compile❳: optional', () => { + let check = compile( + t.object({ + a: t.optional(t.boolean), + }) + ) + + vi.test.concurrent.for([ + /* FAILURE */ + { a: 0 }, + ])('t.optional check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + + vi.test.concurrent.for([ + /* SUCCESS */ + {}, + { a: false }, + ])('t.optional check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + }) + vi.describe('〖⛳️〗‹‹ ❲compile❳: tuple', () => { + let check = compile( + t.tuple( + t.string, + t.number, + ) + ) + + vi.test.concurrent.for([ + /* FAILURE */ + [], + [0, ''], + ])('t.tuple check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + + vi.test.concurrent.for([ + /* SUCCESS */ + ['', 0], + ])('t.tuple check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + }) - // }) + vi.describe('〖⛳️〗‹‹ ❲compile❳: union', () => { + let check = compile( + t.union( + t.string, + t.number, + ) + ) + + vi.test.concurrent.for([ + /* FAILURE */ + false, + ])('t.union check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + + vi.test.concurrent.for([ + /* SUCCESS */ + '', + 0, + ])('t.union check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + }) - let schema = t.object({ - a: t.record(t.object({ - b: t.string, - c: t.tuple() - })) + vi.describe('〖⛳️〗‹‹ ❲compile❳: intersect', () => { + let check = compile( + t.intersect( + t.object({ + a: t.boolean, + }), + t.object({ + b: t.integer, + }) + ) + ) + + vi.test.concurrent.for([ + /* FAILURE */ + {}, + { a: false }, + { b: 0 }, + { a: false, b: '' }, + { a: '', b: 0 }, + ])('t.intersect check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + + vi.test.concurrent.for([ + /* SUCCESS */ + { a: false, b: 0 }, + ])('t.intersect check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) }) - // let jitted = jit(schema) - - // let check = compile(schema) - - // vi.it('TMP', () => { - // vi.expect(jitted).toMatchInlineSnapshot(` - // "function check(value) { - // return ( - // !!value && typeof value === 'object' && !Array.isArray(value) - // && !!value.a && typeof value.a === 'object' && !Array.isArray(value.a) - // && !(value.a instanceof Date) && !(value.a instanceof Uint8Array) - // && Object.entries(value.a).every( - // ([key, value]) => typeof key === 'string' ? !!value && typeof value === 'object' && !Array.isArray(value) - // && typeof value.b === 'string' - // && Array.isArray(value.c) && value.c.length === 0 : true - // ) - // ) - // }" - // `) - // }) - - // vi.test.concurrent.for([ - // // FAILURE - // {}, - // { a: [] }, - // { a: { record: { b: '' } } }, - // { a: { record: { b: '', c: [1] } } }, - // ])('Validation fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) - - // vi.test.concurrent.for([ - // // SUCCESS - // { a: {} }, - // { a: { record: { b: '', c: [] } } }, - // ])('Validation succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + vi.describe('〖⛳️〗‹‹ ❲compile❳: object', () => { + let check = compile( + t.object({ + a: t.boolean, + }) + ) + + vi.test.concurrent.for([ + /* FAILURE */ + {}, + { a: 0 }, + { b: false }, + ])('t.object check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + + vi.test.concurrent.for([ + /* SUCCESS */ + { a: false }, + ])('t.object check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + }) }) @@ -254,19 +350,19 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: jitJs vi.expect(jitJson( '' )).toMatchInlineSnapshot - (`"value === ''"`) + (`"value === """`) vi.expect(jitJson( '\\' )).toMatchInlineSnapshot - (`"value === '\\\\'"`) + (`"value === "\\\\""`) }) vi.it('〖⛳️〗› ❲jitJson❳: objects', () => { vi.expect(jitJson( {} )).toMatchInlineSnapshot - (`"!!value && typeof value === 'object' && !Array.isArray(value)"`) + (`"!!value && typeof value === "object" && !Array.isArray(value)"`) vi.expect(jitJson( { @@ -275,11 +371,11 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: jitJs } )).toMatchInlineSnapshot (` - "!!value && typeof value === 'object' && !Array.isArray(value) + "!!value && typeof value === "object" && !Array.isArray(value) && Array.isArray(value.l) && value.l.length === 1 - && value.l[0] === 'L' - && !!value.m && typeof value.m === 'object' && !Array.isArray(value.m) - && value.m.o === 'O'" + && value.l[0] === "L" + && !!value.m && typeof value.m === "object" && !Array.isArray(value.m) + && value.m.o === "O"" `) }) @@ -327,9 +423,9 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: jitJs (` "Array.isArray(value) && value.length === 3 && value[0] === 1 - && !!value[1] && typeof value[1] === 'object' && !Array.isArray(value[1]) + && !!value[1] && typeof value[1] === "object" && !Array.isArray(value[1]) && value[1].z === 2 - && !!value[2] && typeof value[2] === 'object' && !Array.isArray(value[2]) + && !!value[2] && typeof value[2] === "object" && !Array.isArray(value[2]) && value[2].a === 3 && value[2].b === 3 && Array.isArray(value[2].c) && value[2].c.length === 2 @@ -347,23 +443,23 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: jitJs )).toMatchInlineSnapshot (` "Array.isArray(value) && value.length === 4 - && !!value[0] && typeof value[0] === 'object' && !Array.isArray(value[0]) + && !!value[0] && typeof value[0] === "object" && !Array.isArray(value[0]) && Array.isArray(value[0].ONE) && value[0].ONE.length === 1 && value[0].ONE[0] === true - && !!value[1] && typeof value[1] === 'object' && !Array.isArray(value[1]) + && !!value[1] && typeof value[1] === "object" && !Array.isArray(value[1]) && Array.isArray(value[1].TWO) && value[1].TWO.length === 1 - && !!value[1].TWO[0] && typeof value[1].TWO[0] === 'object' && !Array.isArray(value[1].TWO[0]) + && !!value[1].TWO[0] && typeof value[1].TWO[0] === "object" && !Array.isArray(value[1].TWO[0]) && value[1].TWO[0].B === undefined && value[1].TWO[0].A === null - && !!value[2] && typeof value[2] === 'object' && !Array.isArray(value[2]) + && !!value[2] && typeof value[2] === "object" && !Array.isArray(value[2]) && Array.isArray(value[2].THREE) && value[2].THREE.length === 1 - && !!value[2].THREE[0] && typeof value[2].THREE[0] === 'object' && !Array.isArray(value[2].THREE[0]) + && !!value[2].THREE[0] && typeof value[2].THREE[0] === "object" && !Array.isArray(value[2].THREE[0]) && value[2].THREE[0].A === null && value[2].THREE[0].B === false - && !!value[3] && typeof value[3] === 'object' && !Array.isArray(value[3]) - && value[3].C === '' + && !!value[3] && typeof value[3] === "object" && !Array.isArray(value[3]) + && value[3].C === "" && Array.isArray(value[3].FOUR) && value[3].FOUR.length === 1 - && !!value[3].FOUR[0] && typeof value[3].FOUR[0] === 'object' && !Array.isArray(value[3].FOUR[0]) + && !!value[3].FOUR[0] && typeof value[3].FOUR[0] === "object" && !Array.isArray(value[3].FOUR[0]) && value[3].FOUR[0].B === false && value[3].FOUR[0].A === 1" `) @@ -475,9 +571,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla )).toMatchInlineSnapshot (` "function check(value) { - return ( - false - ) + return false }" `) }) @@ -488,9 +582,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla )).toMatchInlineSnapshot (` "function check(value) { - return ( - true - ) + return true }" `) }) @@ -501,9 +593,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla )).toMatchInlineSnapshot (` "function check(value) { - return ( - true - ) + return true }" `) }) @@ -514,9 +604,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla )).toMatchInlineSnapshot (` "function check(value) { - return ( - value === void 0 - ) + return value === void 0 }" `) }) @@ -527,9 +615,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla )).toMatchInlineSnapshot (` "function check(value) { - return ( - value === null - ) + return value === null }" `) }) @@ -540,9 +626,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla )).toMatchInlineSnapshot (` "function check(value) { - return ( - value === undefined - ) + return value === undefined }" `) }) @@ -553,9 +637,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla )).toMatchInlineSnapshot (` "function check(value) { - return ( - typeof value === 'symbol' - ) + return typeof value === "symbol" }" `) }) @@ -566,13 +648,10 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla )).toMatchInlineSnapshot (` "function check(value) { - return ( - typeof value === 'boolean' - ) + return typeof value === "boolean" }" `) }) - }) @@ -584,9 +663,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - Number.isSafeInteger(value) - ) + return Number.isSafeInteger(value) }" `) }) @@ -597,9 +674,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isSafeInteger(value) && 0 <= value) - ) + return Number.isSafeInteger(value) && 0 <= value }" `) }) @@ -610,9 +685,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isSafeInteger(value) && value <= 1) - ) + return Number.isSafeInteger(value) && value <= 1 }" `) }) @@ -625,9 +698,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isSafeInteger(value) && 0 <= value && value <= 1) - ) + return Number.isSafeInteger(value) && 0 <= value && value <= 1 }" `) @@ -638,9 +709,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isSafeInteger(value) && 0 <= value && value <= 1) - ) + return Number.isSafeInteger(value) && 0 <= value && value <= 1 }" `) }) @@ -651,9 +720,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isSafeInteger(value) && 0 <= value && value <= 1) - ) + return Number.isSafeInteger(value) && 0 <= value && value <= 1 }" `) @@ -662,9 +729,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isSafeInteger(value) && 0 <= value && value <= 1) - ) + return Number.isSafeInteger(value) && 0 <= value && value <= 1 }" `) }) @@ -675,9 +740,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - typeof value === 'bigint' - ) + return typeof value === "bigint" }" `) }) @@ -688,9 +751,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'bigint' && 0n <= value) - ) + return typeof value === "bigint" && 0n <= value }" `) }) @@ -701,9 +762,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'bigint' && value <= 1n) - ) + return typeof value === "bigint" && value <= 1n }" `) }) @@ -716,9 +775,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'bigint' && 0n <= value && value <= 1n) - ) + return typeof value === "bigint" && 0n <= value && value <= 1n }" `) @@ -729,9 +786,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'bigint' && 0n <= value && value <= 1n) - ) + return typeof value === "bigint" && 0n <= value && value <= 1n }" `) }) @@ -742,9 +797,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'bigint' && 0n <= value && value <= 1n) - ) + return typeof value === "bigint" && 0n <= value && value <= 1n }" `) @@ -753,9 +806,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'bigint' && 0n <= value && value <= 1n) - ) + return typeof value === "bigint" && 0n <= value && value <= 1n }" `) }) @@ -766,9 +817,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - Number.isFinite(value) - ) + return Number.isFinite(value) }" `) }) @@ -779,9 +828,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 <= value) - ) + return Number.isFinite(value) && 0 <= value }" `) }) @@ -792,9 +839,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && value <= 1) - ) + return Number.isFinite(value) && value <= 1 }" `) }) @@ -807,9 +852,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 <= value && value <= 1) - ) + return Number.isFinite(value) && 0 <= value && value <= 1 }" `) @@ -820,9 +863,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 <= value && value <= 1) - ) + return Number.isFinite(value) && 0 <= value && value <= 1 }" `) }) @@ -833,9 +874,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 <= value && value <= 1) - ) + return Number.isFinite(value) && 0 <= value && value <= 1 }" `) @@ -844,9 +883,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 <= value && value <= 1) - ) + return Number.isFinite(value) && 0 <= value && value <= 1 }" `) }) @@ -857,9 +894,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 < value) - ) + return Number.isFinite(value) && 0 < value }" `) }) @@ -870,9 +905,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && value < 1) - ) + return Number.isFinite(value) && value < 1 }" `) }) @@ -885,9 +918,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 < value && value < 1) - ) + return Number.isFinite(value) && 0 < value && value < 1 }" `) @@ -898,9 +929,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 < value && value < 1) - ) + return Number.isFinite(value) && 0 < value && value < 1 }" `) }) @@ -913,9 +942,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 <= value && value < 1) - ) + return Number.isFinite(value) && 0 <= value && value < 1 }" `) @@ -926,9 +953,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 <= value && value < 1) - ) + return Number.isFinite(value) && 0 <= value && value < 1 }" `) }) @@ -941,9 +966,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 < value && value <= 1) - ) + return Number.isFinite(value) && 0 < value && value <= 1 }" `) @@ -954,9 +977,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (Number.isFinite(value) && 0 < value && value <= 1) - ) + return Number.isFinite(value) && 0 < value && value <= 1 }" `) }) @@ -967,9 +988,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - typeof value === 'string' - ) + return typeof value === "string" }" `) }) @@ -980,9 +999,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'string' && 0 <= value.length) - ) + return typeof value === "string" && 0 <= value.length }" `) }) @@ -993,9 +1010,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'string' && value.length <= 1) - ) + return typeof value === "string" && value.length <= 1 }" `) }) @@ -1008,9 +1023,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'string' && 0 <= value.length && value.length <= 1) - ) + return typeof value === "string" && 0 <= value.length && value.length <= 1 }" `) @@ -1021,9 +1034,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'string' && 0 <= value.length && value.length <= 1) - ) + return typeof value === "string" && 0 <= value.length && value.length <= 1 }" `) }) @@ -1034,9 +1045,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'string' && 0 <= value.length && value.length <= 1) - ) + return typeof value === "string" && 0 <= value.length && value.length <= 1 }" `) @@ -1045,9 +1054,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound )).toMatchInlineSnapshot (` "function check(value) { - return ( - (typeof value === 'string' && 0 <= value.length && value.length <= 1) - ) + return typeof value === "string" && 0 <= value.length && value.length <= 1 }" `) }) @@ -1165,8 +1172,10 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary (` "function check(value) { return ( + value === undefined - || (value === 1000 || value === 2000 || value === 3000 || value === 4000 || value === 5000 || value === 6000) + || ((value === 1000) || (value === 2000) || (value === 3000) || (value === 4000) || (value === 5000) || (value === 6000)) + ) }" `) @@ -1187,16 +1196,18 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary (` "function check(value) { return ( + value === undefined || ( - value === 1000 - || value === 2000 - || value === 3000 - || value === 4000 - || value === 5000 - || value === 6000 - || value === 9000 + (value === 1000) + || (value === 2000) + || (value === 3000) + || (value === 4000) + || (value === 5000) + || (value === 6000) + || (value === 9000) ) + ) }" `) @@ -1278,7 +1289,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }) - vi.it.only('〖⛳️〗› ❲jit❳: t.union(...)', () => { + vi.it('〖⛳️〗› ❲jit❳: t.union(...)', () => { vi.expect(jit( t.union() @@ -1459,9 +1470,11 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary "function check(value) { return ( value === 9000 - && (!!value && typeof value === "object" && !Array.isArray(value) && value.a === 1 + && ( + !!value && typeof value === "object" && !Array.isArray(value) && value.a === 1 && !!value && typeof value === "object" && !Array.isArray(value) && value.b === 2 - && !!value && typeof value === "object" && !Array.isArray(value) && value.c === 3) + && !!value && typeof value === "object" && !Array.isArray(value) && value.c === 3 + ) ) }" `) @@ -1649,7 +1662,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }) - vi.it.only('〖⛳️〗› ❲jit❳: object(...)', () => { + vi.it('〖⛳️〗› ❲jit❳: object(...)', () => { vi.expect(jit( t.object({}) @@ -1892,7 +1905,6 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary `) }) - }) @@ -1913,36 +1925,38 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: confi (` "function check(value) { return ( - !!value && typeof value === 'object' && !Array.isArray(value) - && - !!value.F && typeof value.F === 'object' && !Array.isArray(value.F) - && true - || !!value.F && typeof value.F === 'object' && !Array.isArray(value.F) - && Number.isFinite(value.F.F) + !!value && typeof value === "object" && !Array.isArray(value) + && ( + (!!value.F && typeof value.F === "object" && !Array.isArray(value.F) && true) + || (!!value.F && typeof value.F === "object" && !Array.isArray(value.F) && Number.isFinite(value.F.F)) + ) ) }" `) configure({ schema: { - treatArraysAsObjects: true, + treatArraysAsObjects: true } }) && vi.expect(jit(schema)).toMatchInlineSnapshot (` "function check(value) { return ( - !!value && typeof value === 'object' - && - !!value.F && typeof value.F === 'object' - && true - || !!value.F && typeof value.F === 'object' - && Number.isFinite(value.F.F) + !!value && typeof value === "object" + && ( + (!!value.F && typeof value.F === "object" && true) + || (!!value.F && typeof value.F === "object" && Number.isFinite(value.F.F)) + ) ) }" `) + + // Cleanup + configure({ schema: { treatArraysAsObjects: false } }) }) vi.it('〖⛳️〗› ❲jit❳: exactOptional', () => { + let schema = t.object({ a: t.number, b: t.optional(t.string), @@ -1952,16 +1966,15 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: confi configure({ schema: { optionalTreatment: 'exactOptional', - treatArraysAsObjects: false, } }) && vi.expect(jit(schema)).toMatchInlineSnapshot (` "function check(value) { return ( - !!value && typeof value === 'object' && !Array.isArray(value) + !!value && typeof value === "object" && !Array.isArray(value) && Number.isFinite(value.a) - && (!Object.hasOwn(value, 'c') || (Number.isFinite(value.c) && 8 <= value.c)) - && (!Object.hasOwn(value, 'b') || typeof value.b === 'string') + && (!Object.hasOwn(value, "c") || (Number.isFinite(value.c) && 8 <= value.c)) + && (!Object.hasOwn(value, "b") || typeof value.b === "string") ) }" `) @@ -1969,16 +1982,15 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: confi configure({ schema: { optionalTreatment: 'presentButUndefinedIsOK', - treatArraysAsObjects: false, } }) && vi.expect(jit(schema)).toMatchInlineSnapshot (` "function check(value) { return ( - !!value && typeof value === 'object' && !Array.isArray(value) + !!value && typeof value === "object" && !Array.isArray(value) && Number.isFinite(value.a) - && value.c === undefined || (Number.isFinite(value.c) && 8 <= value.c) - && (value.b === undefined || typeof value.b === 'string') + && (value.c === undefined || (Number.isFinite(value.c) && 8 <= value.c)) + && (value.b === undefined || typeof value.b === "string") ) }" `) diff --git a/packages/schema-jit-compiler/test/sort.test.ts b/packages/schema-jit-compiler/test/sort.test.ts index 47186105..7244abeb 100644 --- a/packages/schema-jit-compiler/test/sort.test.ts +++ b/packages/schema-jit-compiler/test/sort.test.ts @@ -31,29 +31,31 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () = vi.expect(jit(t.object(SHALLOW_ORDER))).toMatchInlineSnapshot(` "function check(value) { return ( - !!value && typeof value === 'object' && !Array.isArray(value) + !!value && typeof value === "object" && !Array.isArray(value) && false && true && true && value["30"] === void 0 && value["40"] === undefined && value["50"] === null - && typeof value["60"] === 'symbol' - && typeof value["70"] === 'boolean' + && typeof value["60"] === "symbol" + && typeof value["70"] === "boolean" && Number.isSafeInteger(value["80"]) - && typeof value["90"] === 'bigint' + && typeof value["90"] === "bigint" && Number.isFinite(value["100"]) - && typeof value["110"] === 'string' + && typeof value["110"] === "string" && (value["120"] === undefined || false) - && true - && false + && (true) + && (false) && Array.isArray(value["150"]) && value["150"].length === 0 - && !!value["160"] && typeof value["160"] === 'object' && !Array.isArray(value["160"]) + && !!value["160"] && typeof value["160"] === "object" && !Array.isArray(value["160"]) && Array.isArray(value["170"]) && value["170"].every((value) => false) - && !!value["180"] && typeof value["180"] === 'object' && !Array.isArray(value["180"]) + && !!value["180"] && typeof value["180"] === "object" && !Array.isArray(value["180"]) && !(value["180"] instanceof Date) && !(value["180"] instanceof Uint8Array) - && Object.entries(value["180"]).every(([key, value]) => typeof key === 'string' ? false : true) - && !!value["190"] && typeof value["190"] === 'object' && !Array.isArray(value["190"]) + && Object.entries(value["180"]).every(([key, value]) => + typeof key === "string" && false + ) + && !!value["190"] && typeof value["190"] === "object" && !Array.isArray(value["190"]) ) }" `) From bdefc5730f090f0415b625d5cfc23d495f3cc4e2 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 17 Apr 2025 03:34:06 -0500 Subject: [PATCH 39/45] refactor(jit): inline some functions, remove dead code --- packages/schema-jit-compiler/src/exports.ts | 31 +- packages/schema-jit-compiler/src/functor.ts | 278 +++----- packages/schema-jit-compiler/src/index.ts | 1 + packages/schema-jit-compiler/src/jit.ts | 587 +++++++-------- packages/schema-jit-compiler/src/json.ts | 128 ++-- packages/schema-jit-compiler/src/shared.ts | 71 ++ packages/schema-jit-compiler/src/sort.ts | 233 ------ packages/schema-jit-compiler/test/TODO.ts | 138 ++-- .../schema-jit-compiler/test/functor.test.ts | 9 - packages/schema-jit-compiler/test/jit.test.ts | 669 ++++++------------ .../schema-jit-compiler/test/json.test.ts | 332 ++++++++- .../schema-jit-compiler/test/sort.test.ts | 50 +- 12 files changed, 1157 insertions(+), 1370 deletions(-) create mode 100644 packages/schema-jit-compiler/src/shared.ts delete mode 100644 packages/schema-jit-compiler/src/sort.ts delete mode 100644 packages/schema-jit-compiler/test/functor.test.ts diff --git a/packages/schema-jit-compiler/src/exports.ts b/packages/schema-jit-compiler/src/exports.ts index e468f38b..1c8cbf77 100644 --- a/packages/schema-jit-compiler/src/exports.ts +++ b/packages/schema-jit-compiler/src/exports.ts @@ -1,30 +1,13 @@ export * from './version.js' -export type { - Algebra, - Index, -} from './functor.js' export { - Functor, - defaultIndex, - indexAccessor, - keyAccessor, - fold, - makeFold, - makeFunctor, -} from './functor.js' -export { - jit, - jitJson, + generate, compile, + WeightByTypeName, } from './jit.js' export * as Json from './json.js' +export type { Index } from './shared.js' export { - getWeight as getJsonWeight, - sort as sortJson, -} from './json.js' -export { - print, - sort as sortSchema, - WeightByTypeName, -} from './sort.js' - + defaultIndex, + indexAccessor, + keyAccessor, +} from './shared.js' diff --git a/packages/schema-jit-compiler/src/functor.ts b/packages/schema-jit-compiler/src/functor.ts index 6506e8fc..37eec719 100644 --- a/packages/schema-jit-compiler/src/functor.ts +++ b/packages/schema-jit-compiler/src/functor.ts @@ -1,11 +1,23 @@ import type * as T from '@traversable/registry' -import { fn, isValidIdentifier, parseKey, symbol, URI } from '@traversable/registry' +import { fn, symbol, URI } from '@traversable/registry' import { t } from '@traversable/schema-core' -/** @internal */ -let isProp = t.union(t.string, t.number) +import { indexAccessor, keyAccessor } from './shared.js' -export type F = +export type Index = { + siblingCount: number + offset: number + dataPath: (string | number)[] + isOptional: boolean + isRoot: boolean + schemaPath: (keyof any)[] + varName: string +} + +export interface Free extends T.HKT { [-1]: IR } +export type Algebra = T.IndexedAlgebra + +export type IR = | t.Leaf | t.eq | t.array @@ -14,39 +26,7 @@ export type F = | t.union | t.intersect | t.tuple - | t.object<[k: string, v: T][]> - -export type Fixpoint = - | t.Leaf - | t.eq - | t.array - | t.record - | t.optional - | t.union - | t.intersect - | t.tuple - | t.object<[k: string, v: Fixpoint][]> - -export interface Free extends T.HKT { [-1]: F } - -export function keyAccessor(key: keyof any | undefined, $: Index) { - return typeof key === 'string' ? isValidIdentifier(key) ? $.isOptional - ? `?.${key}` - : `.${key}` - : `[${parseKey(key)}]` - : '' -} - -// Reading `x` to access the "preSortIndex" is a hack to make sure -// we preserve the original order of the tuple, even while sorting -export function indexAccessor(index: keyof any | undefined, $: { isOptional?: boolean }, x?: any) { - return 'preSortIndex' in x - ? $.isOptional ? `?.[${x.preSortIndex}]` : `[${x.preSortIndex}]` - : typeof index === 'number' ? $.isOptional - ? `?.[${index}]` - : `[${index}]` - : '' -} + | t.object<[k: string, T][]> export let defaultIndex: Index = { siblingCount: 0, @@ -58,158 +38,110 @@ export let defaultIndex: Index = { isOptional: false, } -/** @internal */ -let map - : T.Functor['map'] - = (f) => function jitMap(xs) { - switch (true) { - default: return fn.exhaustive(xs) - case t.isNullary(xs): return xs - case t.isBoundable(xs): return xs - case xs.tag === URI.eq: return t.eq(xs.def as never) - case xs.tag === URI.optional: return t.optional.def(f(xs.def)) - case xs.tag === URI.array: return t.array.def(f(xs.def)) - case xs.tag === URI.record: return t.record.def(f(xs.def)) - case xs.tag === URI.union: return t.union.def(xs.def.map(f)) - case xs.tag === URI.intersect: return t.intersect.def(xs.def.map(f)) - case xs.tag === URI.tuple: return t.tuple.def(xs.def.map(f)) - case xs.tag === URI.object: return t.object.def( - xs.def.map(([k, v]) => [k, f(v)] satisfies [any, any]), - undefined, - xs.opt, - ) - } - } -export function makeFunctor(updateIndex: UpdateIndex): T.Functor.Ix { - return { - map, - mapWithIndex(f) { - return function jitMap(xs, ix) { - switch (true) { - default: return fn.exhaustive(xs) - case t.isNullary(xs): return xs - case t.isBoundable(xs): return xs - case xs.tag === URI.eq: return t.eq(xs.def as never) - case xs.tag === URI.optional: return t.optional.def(f(xs.def, updateIndex(ix, xs, [symbol.optional]))) - case xs.tag === URI.array: return t.array.def(f(xs.def, updateIndex(ix, xs, [symbol.array]))) - case xs.tag === URI.record: return t.record.def(f(xs.def, updateIndex(ix, xs, [symbol.record]))) - case xs.tag === URI.union: return t.union.def(xs.def.map((x, i) => f(x, updateIndex(ix, xs, [symbol.union, i])))) - case xs.tag === URI.intersect: return t.intersect.def(xs.def.map((x, i) => f(x, updateIndex(ix, xs, [symbol.intersect, i])))) - case xs.tag === URI.tuple: return t.tuple.def(xs.def.map((x, i) => f(x, updateIndex(ix, xs, [i])))) - case xs.tag === URI.object: return t.object.def( - xs.def.map(([k, v]) => [k, f(v, updateIndex(ix, xs, [k]))] satisfies [any, any]), - undefined, - xs.opt, - ) - } - } - } +let map: T.Functor['map'] = (f) => (x) => { + switch (true) { + default: return fn.exhaustive(x) + case t.isNullary(x): return x + case t.isBoundable(x): return x + case x.tag === URI.eq: return t.eq.def(x.def as never) + case x.tag === URI.optional: return t.optional.def(f(x.def)) + case x.tag === URI.array: return t.array.def(f(x.def)) + case x.tag === URI.record: return t.record.def(f(x.def)) + case x.tag === URI.union: return t.union.def(fn.map(x.def, f)) + case x.tag === URI.intersect: return t.intersect.def(fn.map(x.def, f)) + case x.tag === URI.tuple: return t.tuple.def(fn.map(x.def, f)) + case x.tag === URI.object: return t.object.def( + fn.map(x.def, ([k, v]) => [k, f(v)] satisfies [any, any]), + undefined, + x.opt, + ) } } -export function makeFold( - algebra: Algebra, - updateIndex: UpdateIndex -): (term: F, ix: Opts) => T - -export function makeFold( - algebra: Algebra, - updateIndex: UpdateIndex -): (term: F, ix: Index) => T { - return fn.cataIx(makeFunctor(updateIndex))(algebra) -} - -export type Index = { - siblingCount: number - offset: number - dataPath: (string | number)[] - isOptional: boolean - isRoot: boolean - schemaPath: (keyof any)[] - varName: string -} - -export type Algebra = T.IndexedAlgebra -export type UpdateIndex = (prev: Opts, x: F, i: (keyof any)[]) => Opts - -export interface Functor extends T.Functor.Ix {} -export declare namespace Functor { export { Algebra, Index } } - -export let Functor = makeFunctor((prev: Index, x, ix) => { +let mapWithIndex: T.Functor.Ix['mapWithIndex'] = (f) => (xs, ix) => { switch (true) { - default: return fn.exhaustive(x) - case t.isNullary(x): return prev - case t.isBoundable(x): return prev - case x.tag === URI.eq: return prev - case x.tag === URI.optional: return { - dataPath: prev.dataPath, - offset: prev.offset + 2, + default: return fn.exhaustive(xs) + case t.isNullary(xs): return xs + case t.isBoundable(xs): return xs + case xs.tag === URI.eq: return xs as never + case xs.tag === URI.optional: return t.optional.def(f(xs.def, { + dataPath: ix.dataPath, isOptional: true, isRoot: false, - schemaPath: [...prev.schemaPath, symbol.optional], + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, symbol.optional], siblingCount: 0, - varName: prev.varName, - } - case x.tag === URI.array: return { - dataPath: prev.dataPath, - offset: prev.offset + 2, - isOptional: prev.isOptional, + varName: ix.varName, + })) + case xs.tag === URI.array: return t.array.def(f(xs.def, { + dataPath: ix.dataPath, + isOptional: ix.isOptional, isRoot: false, - schemaPath: [...prev.schemaPath, symbol.array], + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, symbol.array], siblingCount: 0, varName: 'value', - } - case x.tag === URI.record: return { - dataPath: prev.dataPath, - offset: prev.offset + 2, - isOptional: prev.isOptional, + })) + case xs.tag === URI.record: return t.record.def(f(xs.def, { + dataPath: ix.dataPath, + isOptional: ix.isOptional, isRoot: false, - schemaPath: [...prev.schemaPath, symbol.record], + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, symbol.array], siblingCount: 0, - varName: prev.varName, - } - case x.tag === URI.union: return { - dataPath: prev.dataPath, - offset: prev.offset + 2, - isOptional: prev.isOptional, + varName: 'value', + })) + case xs.tag === URI.union: return t.union.def(fn.map(xs.def, (x, i) => f(x, { + dataPath: ix.dataPath, + isOptional: ix.isOptional, isRoot: false, - schemaPath: [...prev.schemaPath, symbol.union, ...ix], - siblingCount: Math.max(x.def.length - 1, 0), - varName: prev.varName, - } - case x.tag === URI.intersect: return { - dataPath: prev.dataPath, - offset: prev.offset + 2, - isOptional: prev.isOptional, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, i], + siblingCount: Math.max(xs.def.length - 1, 0), + varName: ix.varName, + }))) + case xs.tag === URI.intersect: return t.intersect.def(fn.map(xs.def, (x, i) => f(x, { + dataPath: ix.dataPath, + isOptional: ix.isOptional, isRoot: false, - schemaPath: [...prev.schemaPath, symbol.intersect, ...ix], - siblingCount: Math.max(x.def.length - 1, 0), - varName: prev.varName, - } - case x.tag === URI.tuple: return { - dataPath: [...prev.dataPath, ...ix.filter(isProp)], - offset: prev.offset + 2, - isOptional: prev.isOptional, - isRoot: false, - schemaPath: [...prev.schemaPath, ...ix], - siblingCount: Math.max(x.def.length - 1, 0), - varName: prev.varName + indexAccessor(ix[0], prev), - } - case x.tag === URI.object: { - return { - dataPath: [...prev.dataPath, ...ix.filter(isProp)], - offset: prev.offset + 2, - isOptional: prev.isOptional, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, i], + siblingCount: Math.max(xs.def.length - 1, 0), + varName: ix.varName, + }))) + case xs.tag === URI.tuple: + return t.tuple.def(fn.map(xs.def, (x, i) => f(x, { + dataPath: [...ix.dataPath, i], + isOptional: ix.isOptional, isRoot: false, - schemaPath: [...prev.schemaPath, ...ix], - siblingCount: Math.max(Object.keys(x.def).length - 1, 0), - varName: prev.varName + keyAccessor(ix[0], prev), - } + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, i], + siblingCount: Math.max(xs.def.length - 1, 0), + /** + * Passing `x` to `indexAccessor` is a hack to make sure + * we preserve the original order of the tuple while we're + * applying a sorting optimization + */ + varName: ix.varName + indexAccessor(i, ix, x), + }))) + case xs.tag === URI.object: { + return t.object.def( + fn.map(xs.def, ([k, v]) => [k, f(v, { + dataPath: [...ix.dataPath, k], + isOptional: ix.isOptional, + isRoot: false, + offset: ix.offset + 2, + schemaPath: [...ix.schemaPath, k], + siblingCount: Math.max(Object.keys(xs.def).length - 1, 0), + varName: ix.varName + keyAccessor(k, ix), + })] satisfies [any, any]), + undefined, + xs.opt, + ) } } -}) +} -export let fold - : (algebra: T.IndexedAlgebra) => (schema: S, index?: Index) => T - = (algebra) => (schema, index = defaultIndex) => fn.cataIx(Functor)(algebra)(schema as never, index) +export let Functor: T.Functor.Ix = { map, mapWithIndex } +export let fold = (algebra: Algebra) => (x: IR) => fn.cataIx(Functor)(algebra)(x, defaultIndex) diff --git a/packages/schema-jit-compiler/src/index.ts b/packages/schema-jit-compiler/src/index.ts index 410a4bcb..96cd0f4f 100644 --- a/packages/schema-jit-compiler/src/index.ts +++ b/packages/schema-jit-compiler/src/index.ts @@ -1 +1,2 @@ export * from './exports.js' +export * as Jit from './exports.js' diff --git a/packages/schema-jit-compiler/src/jit.ts b/packages/schema-jit-compiler/src/jit.ts index 4bb293ea..927aaa14 100644 --- a/packages/schema-jit-compiler/src/jit.ts +++ b/packages/schema-jit-compiler/src/jit.ts @@ -1,295 +1,306 @@ import type * as T from '@traversable/registry' -import { escape, fn, getConfig, parseKey, URI } from '@traversable/registry' +import { fn, getConfig, parseKey, typeName, URI } from '@traversable/registry' import { t } from '@traversable/schema-core' -import * as Weighted from './sort.js' +import type { Algebra, IR } from './functor.js' +import { fold } from './functor.js' import * as Json from './json.js' -import type { Index } from './functor.js' - -export type Context = { - VAR: string - RETURN: string - TABSTOP: string - JOIN: string - indent(numberOfSpaces: number): string - dedent(numberOfSpaces: number): string - join(numberOfSpaces: number): string -} +import { buildContext } from './shared.js' export let MAX_WIDTH = 120 -export function buildContext(ix: T.Require): Context { - let VAR = ix.varName - let indent = (numberOfSpaces: number) => `\r${' '.repeat(Math.max(ix.offset + numberOfSpaces, 0))}` - let dedent = (numberOfSpaces: number) => `\r${' '.repeat(Math.max(ix.offset - numberOfSpaces, 0))}` - let join = (numberOfSpaces: number) => indent(numberOfSpaces) + '&& ' - let JOIN = join(2) - let RETURN = indent(2) - let TABSTOP = indent(4) - return { dedent, indent, join, JOIN, RETURN, TABSTOP, VAR } -} - -export namespace Jit { - export declare namespace checkJson { - type ReturnTypeLowerBound = { - tag: URI.bottom | URI.array | URI.object - def: unknown +export let WeightByTypeName = { + never: 0, + any: 10, + unknown: 20, + void: 30, + undefined: 40, + null: 50, + symbol: 60, + boolean: 70, + integer: 80, + bigint: 90, + number: 100, + string: 110, + optional: 120, + intersect: 130, + union: 140, + tuple: 150, + object: 160, + array: 170, + record: 180, + eq: 190, +} as const + +export let interpreter: Algebra = (x, ix) => { + let ctx = buildContext(ix) + let { VAR, indent } = ctx + let { schema: $ } = getConfig() + let NON_ARRAY_CHECK = $.treatArraysAsObjects ? '' : ` && !Array.isArray(${VAR})` + let IS_EXACT_OPTIONAL = $.optionalTreatment === 'exactOptional' + switch (true) { + default: return fn.exhaustive(x) + case x.tag === URI.never: return 'false' + case x.tag === URI.any: return 'true' + case x.tag === URI.unknown: return 'true' + case x.tag === URI.void: return `${VAR} === void 0` + case x.tag === URI.null: return `${VAR} === null` + case x.tag === URI.undefined: return `${VAR} === undefined` + case x.tag === URI.symbol: return `typeof ${VAR} === "symbol"` + case x.tag === URI.boolean: return `typeof ${VAR} === "boolean"` + case x.tag === URI.integer: { + let CHECK = `Number.isSafeInteger(${VAR})` + let MIN_CHECK = t.number(x.minimum) ? ` && ${x.minimum} <= ${VAR}` : '' + let MAX_CHECK = t.number(x.maximum) ? ` && ${VAR} <= ${x.maximum}` : '' + let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' + let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' + return '' + + OPEN + + CHECK + + MIN_CHECK + + MAX_CHECK + + CLOSE } - } - - export function checkJson(x: Json.IR>, ix: Json.Index): Json.IR - export function checkJson(x: Json.IR>, ix: Json.Index): checkJson.ReturnTypeLowerBound { - let { VAR, join } = buildContext(ix) - switch (true) { - default: return fn.exhaustive(x) - case x.tag === URI.bottom: { - let BODY = VAR + ' === ' - switch (true) { - default: return fn.exhaustive(x.def) - case x.def === null: BODY += 'null'; break - case x.def === undefined: BODY += 'undefined'; break - case x.def === true: BODY += 'true'; break - case x.def === false: BODY += 'false'; break - case x.def === 0: BODY += 1 / x.def === Number.NEGATIVE_INFINITY ? '-0' : '+0'; break - case typeof x.def === 'string': BODY += `"${escape(x.def)}"`; break - case typeof x.def === 'number': BODY += String(x.def); break - } - return { - tag: URI.bottom, - def: BODY, - } - } - - case x.tag === URI.array: return { - tag: URI.array, - def: '' - + `Array.isArray(${VAR}) && ` - + `${VAR}.length === ${x.def.length}` - + (x.def.length === 0 ? '' : x.def.map((v, i) => i === 0 ? join(0) + v.def : v.def).join(join(0))), - } - case x.tag === URI.object: return { - tag: URI.object, - def: '' - + `!!${VAR} && typeof ${VAR} === "object" && !Array.isArray(${VAR})` - + (x.def.length === 0 ? '' : x.def.map(([, v], i) => i === 0 ? join(0) + v.def : v.def).join(join(0))), - } + case x.tag === URI.bigint: { + let CHECK = `typeof ${VAR} === "bigint"` + let MIN_CHECK = t.bigint(x.minimum) ? ` && ${x.minimum}n <= ${VAR}` : '' + let MAX_CHECK = t.bigint(x.maximum) ? ` && ${VAR} <= ${x.maximum}n` : '' + let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' + let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' + return '' + + OPEN + + CHECK + + MIN_CHECK + + MAX_CHECK + + CLOSE } - } - - export let check: Weighted.Algebra = (x, ix) => { - let ctx = buildContext(ix) - let { VAR, indent } = ctx - let { schema: $ } = getConfig() - let NON_ARRAY_CHECK = $.treatArraysAsObjects ? '' : ` && !Array.isArray(${VAR})` - let IS_EXACT_OPTIONAL = $.optionalTreatment === 'exactOptional' - switch (true) { - default: return fn.exhaustive(x) - case x.tag === URI.never: return 'false' - case x.tag === URI.any: return 'true' - case x.tag === URI.unknown: return 'true' - case x.tag === URI.void: return `${VAR} === void 0` - case x.tag === URI.null: return `${VAR} === null` - case x.tag === URI.undefined: return `${VAR} === undefined` - case x.tag === URI.symbol: return `typeof ${VAR} === "symbol"` - case x.tag === URI.boolean: return `typeof ${VAR} === "boolean"` - - case x.tag === URI.integer: { - let CHECK = `Number.isSafeInteger(${VAR})` - let MIN_CHECK = t.number(x.minimum) ? ` && ${x.minimum} <= ${VAR}` : '' - let MAX_CHECK = t.number(x.maximum) ? ` && ${VAR} <= ${x.maximum}` : '' - let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' - let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' - return '' - + OPEN - + CHECK - + MIN_CHECK - + MAX_CHECK - + CLOSE - } - - case x.tag === URI.bigint: { - let CHECK = `typeof ${VAR} === "bigint"` - let MIN_CHECK = t.bigint(x.minimum) ? ` && ${x.minimum}n <= ${VAR}` : '' - let MAX_CHECK = t.bigint(x.maximum) ? ` && ${VAR} <= ${x.maximum}n` : '' - let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' - let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' - return '' - + OPEN - + CHECK - + MIN_CHECK - + MAX_CHECK - + CLOSE - } - case x.tag === URI.number: { - let CHECK = `Number.isFinite(${VAR})` - let MIN_CHECK = t.number(x.exclusiveMinimum) - ? ` && ${x.exclusiveMinimum} < ${VAR}` - : t.number(x.minimum) ? ` && ${x.minimum} <= ${VAR}` : '' - let MAX_CHECK = t.number(x.exclusiveMaximum) - ? ` && ${VAR} < ${x.exclusiveMaximum}` - : t.number(x.maximum) ? ` && ${VAR} <= ${x.maximum}` : '' - let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' - let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' - return '' - + OPEN - + CHECK - + MIN_CHECK - + MAX_CHECK - + CLOSE - } - - case x.tag === URI.string: { - let CHECK = `typeof ${VAR} === "string"` - let MIN_CHECK = t.number(x.minLength) ? ` && ${x.minLength} <= ${VAR}.length` : '' - let MAX_CHECK = t.number(x.maxLength) ? ` && ${VAR}.length <= ${x.maxLength}` : '' - let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' - let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' - return '' - + OPEN - + CHECK - + MIN_CHECK - + MAX_CHECK - + CLOSE - } + case x.tag === URI.number: { + let CHECK = `Number.isFinite(${VAR})` + let MIN_CHECK = t.number(x.exclusiveMinimum) + ? ` && ${x.exclusiveMinimum} < ${VAR}` + : t.number(x.minimum) ? ` && ${x.minimum} <= ${VAR}` : '' + let MAX_CHECK = t.number(x.exclusiveMaximum) + ? ` && ${VAR} < ${x.exclusiveMaximum}` + : t.number(x.maximum) ? ` && ${VAR} <= ${x.maximum}` : '' + let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' + let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' + return '' + + OPEN + + CHECK + + MIN_CHECK + + MAX_CHECK + + CLOSE + } - case x.tag === URI.eq: { - return jitJson( - x.def, { - ...ix, - varName: VAR, - offset: ix.offset + 2, - }) - } + case x.tag === URI.string: { + let CHECK = `typeof ${VAR} === "string"` + let MIN_CHECK = t.number(x.minLength) ? ` && ${x.minLength} <= ${VAR}.length` : '' + let MAX_CHECK = t.number(x.maxLength) ? ` && ${VAR}.length <= ${x.maxLength}` : '' + let OPEN = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? '(' : '' + let CLOSE = MIN_CHECK.length > 0 || MAX_CHECK.length > 0 ? ')' : '' + return '' + + OPEN + + CHECK + + MIN_CHECK + + MAX_CHECK + + CLOSE + } - case x.tag === URI.optional: { - if (IS_EXACT_OPTIONAL) return x.def - else { - let CHECK = `${VAR} === undefined` - let WIDTH = ix.offset + CHECK.length + ' || '.length + x.def.length - let SINGLE_LINE = WIDTH < MAX_WIDTH - let OPEN = SINGLE_LINE ? '(' : ('(' + indent(2)) - let CLOSE = SINGLE_LINE ? ')' : (indent(0) + ')') - let BODY = SINGLE_LINE ? (CHECK + ' || ' + x.def) : (CHECK + indent(2) + '|| ' + x.def) - return '' - + OPEN - + BODY - + CLOSE - } - } + case x.tag === URI.eq: { + return Json.generate( + x.def, { + ...ix, + varName: VAR, + offset: ix.offset + 2, + }) + } - case x.tag === URI.array: { - let MIN_CHECK = t.number(x.minLength) ? `&& ${x.minLength} < ${VAR}.length` : '' - let MAX_CHECK = t.number(x.maxLength) ? `&& ${VAR}.length < ${x.maxLength}` : '' - let OUTER_CHECK = `Array.isArray(${VAR})${MIN_CHECK}${MAX_CHECK} && ` - let INNER_CHECK = `${VAR}.every((value) => ` - let WIDTH = ix.offset + OUTER_CHECK.length + INNER_CHECK.length + x.def.length + case x.tag === URI.optional: { + if (IS_EXACT_OPTIONAL) return x.def + else { + let CHECK = `${VAR} === undefined` + let WIDTH = ix.offset + CHECK.length + ' || '.length + x.def.length let SINGLE_LINE = WIDTH < MAX_WIDTH - let OPEN = SINGLE_LINE ? '' : indent(4) - let CLOSE = SINGLE_LINE ? ')' : (indent(2) + ')') + let OPEN = SINGLE_LINE ? '(' : ('(' + indent(2)) + let CLOSE = SINGLE_LINE ? ')' : (indent(0) + ')') + let BODY = SINGLE_LINE ? (CHECK + ' || ' + x.def) : (CHECK + indent(2) + '|| ' + x.def) return '' - + OUTER_CHECK - + INNER_CHECK + OPEN - + x.def + + BODY + CLOSE } + } - case x.tag === URI.record: { - let OUTER_CHECK = '' - + `!!${VAR} && typeof ${VAR} === "object"${NON_ARRAY_CHECK} ${indent(2)}&& ` - + `!(${VAR} instanceof Date) && !(${VAR} instanceof Uint8Array) ${indent(2)}&& ` - let INNER_CHECK = `Object.entries(${VAR}).every(([key, value]) => ` - let KEY_CHECK = 'typeof key === "string" && ' - let WIDTH = ix.offset + OUTER_CHECK.length + INNER_CHECK.length + x.def.length - let SINGLE_LINE = WIDTH < MAX_WIDTH - let OPEN = SINGLE_LINE ? KEY_CHECK : (indent(4) + KEY_CHECK) - let CLOSE = SINGLE_LINE ? ')' : (indent(2) + ')') - return '' - + OUTER_CHECK - + INNER_CHECK - + OPEN - + x.def - + CLOSE - } + case x.tag === URI.array: { + let MIN_CHECK = t.number(x.minLength) ? `&& ${x.minLength} < ${VAR}.length` : '' + let MAX_CHECK = t.number(x.maxLength) ? `&& ${VAR}.length < ${x.maxLength}` : '' + let OUTER_CHECK = `Array.isArray(${VAR})${MIN_CHECK}${MAX_CHECK} && ` + let INNER_CHECK = `${VAR}.every((value) => ` + let WIDTH = ix.offset + OUTER_CHECK.length + INNER_CHECK.length + x.def.length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let OPEN = SINGLE_LINE ? '' : indent(4) + let CLOSE = SINGLE_LINE ? ')' : (indent(2) + ')') + return '' + + OUTER_CHECK + + INNER_CHECK + + OPEN + + x.def + + CLOSE + } - case x.tag === URI.union: { - let CHILD_COUNT = x.def.length - let WIDTH = ix.offset + x.def.join(' || ').length - let SINGLE_LINE = WIDTH < MAX_WIDTH - let OPEN = SINGLE_LINE || ix.isRoot ? '(' : ('(' + indent(2)) - let CLOSE = SINGLE_LINE || ix.isRoot ? ')' : (indent(0) + ')') - let BODY = CHILD_COUNT === 0 ? 'false' - : SINGLE_LINE ? x.def.map((v) => '(' + v + ')').join(' || ') - : x.def.map((v) => '(' + v + ')').join(indent(2) + '|| ') - return '' - + OPEN - + BODY - + CLOSE - } + case x.tag === URI.record: { + let OUTER_CHECK = '' + + `!!${VAR} && typeof ${VAR} === "object"${NON_ARRAY_CHECK} ${indent(2)}&& ` + + `!(${VAR} instanceof Date) && !(${VAR} instanceof Uint8Array) ${indent(2)}&& ` + let INNER_CHECK = `Object.entries(${VAR}).every(([key, value]) => ` + let KEY_CHECK = 'typeof key === "string" && ' + let WIDTH = ix.offset + OUTER_CHECK.length + INNER_CHECK.length + x.def.length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let OPEN = SINGLE_LINE ? KEY_CHECK : (indent(4) + KEY_CHECK) + let CLOSE = SINGLE_LINE ? ')' : (indent(2) + ')') + return '' + + OUTER_CHECK + + INNER_CHECK + + OPEN + + x.def + + CLOSE + } - case x.tag === URI.intersect: { - let CHILD_COUNT = x.def.length - let WIDTH = ix.offset + x.def.join(' || ').length - let SINGLE_LINE = WIDTH < MAX_WIDTH - let OPEN = SINGLE_LINE || ix.isRoot ? '(' : ('(' + indent(2)) - let CLOSE = SINGLE_LINE || ix.isRoot ? ')' : (indent(0) + ')') - let BODY = CHILD_COUNT === 0 ? 'true' : SINGLE_LINE ? x.def.join(' && ') : x.def.join(indent(2) + '&& ') - return '' - + OPEN - + BODY - + CLOSE - } + case x.tag === URI.union: { + let CHILD_COUNT = x.def.length + let WIDTH = ix.offset + x.def.join(' || ').length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let OPEN = SINGLE_LINE || ix.isRoot ? '(' : ('(' + indent(2)) + let CLOSE = SINGLE_LINE || ix.isRoot ? ')' : (indent(0) + ')') + let BODY = CHILD_COUNT === 0 ? 'false' + : SINGLE_LINE ? x.def.map((v) => '(' + v + ')').join(' || ') + : x.def.map((v) => '(' + v + ')').join(indent(2) + '|| ') + return '' + + OPEN + + BODY + + CLOSE + } - case x.tag === URI.tuple: { - let CHILD_COUNT = x.def.length - let CHECK = `Array.isArray(${VAR}) && ${VAR}.length === ${CHILD_COUNT}` - let WIDTH = ix.offset + CHECK.length + x.def.join(' && ').length - let SINGLE_LINE = WIDTH < MAX_WIDTH - let JOIN = SINGLE_LINE ? '' : indent(2) - let CHILDREN = CHILD_COUNT === 0 ? '' : x.def.map((v) => JOIN + (SINGLE_LINE ? ' && ' : '&& ') + v).join('') - return CHECK + CHILDREN - } + case x.tag === URI.intersect: { + let CHILD_COUNT = x.def.length + let WIDTH = ix.offset + x.def.join(' || ').length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let OPEN = SINGLE_LINE || ix.isRoot ? '(' : ('(' + indent(2)) + let CLOSE = SINGLE_LINE || ix.isRoot ? ')' : (indent(0) + ')') + let BODY = CHILD_COUNT === 0 ? 'true' + : SINGLE_LINE ? x.def.join(' && ') + : x.def.join(indent(2) + '&& ') + return '' + + OPEN + + BODY + + CLOSE + } - case x.tag === URI.object: { - let CHILD_COUNT = x.def.length - let CHECK = `!!${VAR} && typeof ${VAR} === "object"${NON_ARRAY_CHECK}` - let OPTIONAL_KEYS = Array.of().concat(x.opt) - let CHILDREN = x.def.map( - ([k, v]) => IS_EXACT_OPTIONAL && OPTIONAL_KEYS.includes(k) - ? `(!Object.hasOwn(${VAR}, "${parseKey(k)}") || ${v})` - : v - ) - let WIDTH = ix.offset + CHECK.length + CHILDREN.join(' && ').length - let SINGLE_LINE = WIDTH < MAX_WIDTH - let JOIN = SINGLE_LINE ? '' : indent(2) - let BODY = CHILD_COUNT === 0 ? '' : CHILDREN.map((v) => JOIN + (SINGLE_LINE ? ' && ' : '&& ') + v).join('') + case x.tag === URI.tuple: { + let CHILD_COUNT = x.def.length + let CHECK = `Array.isArray(${VAR}) && ${VAR}.length === ${CHILD_COUNT}` + let WIDTH = ix.offset + CHECK.length + x.def.join(' && ').length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let JOIN = SINGLE_LINE ? '' : indent(2) + let BODY = CHILD_COUNT === 0 ? '' : x.def.map((v) => JOIN + (SINGLE_LINE ? ' && ' : '&& ') + v).join('') + return CHECK + BODY + } - return '' - + CHECK - + BODY - } + case x.tag === URI.object: { + let CHILD_COUNT = x.def.length + let CHECK = `!!${VAR} && typeof ${VAR} === "object"${NON_ARRAY_CHECK}` + let OPTIONAL_KEYS = Array.of().concat(x.opt) + let CHILDREN = x.def.map( + ([k, v]) => IS_EXACT_OPTIONAL && OPTIONAL_KEYS.includes(k) + ? `(!Object.hasOwn(${VAR}, "${parseKey(k)}") || ${v})` + : v + ) + let WIDTH = ix.offset + CHECK.length + CHILDREN.join(' && ').length + let SINGLE_LINE = WIDTH < MAX_WIDTH + let JOIN = SINGLE_LINE ? '' : indent(2) + let BODY = CHILD_COUNT === 0 ? '' : CHILDREN.map((v) => JOIN + (SINGLE_LINE ? ' && ' : '&& ') + v).join('') + return CHECK + BODY } } } -export function jitJson(json: Json.Any, index?: Index): string -export function jitJson(json: Json.Any, index?: Index) { - return fn.pipe( - json, - Json.sort, - (sorted) => Json.fold(Jit.checkJson)(sorted, index).def, - ) +let weightComparator: T.Comparator = (l, r) => { + let lw = getWeight(l) + let rw = getWeight(r) + return lw < rw ? -1 : rw < lw ? +1 : 0 +} + +let aggregateWeights + : (acc: number, curr: t.Schema) => number + = (acc, curr) => Math.max(acc, getWeight(curr)) + +function getWeight(x: IR): number +function getWeight(x: t.Schema): number +function getWeight(x: IR): number { + let w = WeightByTypeName[typeName(x)] + switch (true) { + default: return fn.exhaustive(x) + case t.isNullary(x): return w + case t.isBoundable(x): return w + case x.tag === URI.eq: return w + case x.tag === URI.optional: return w + getWeight(x.def) + case x.tag === URI.array: return w + getWeight(x.def) + case x.tag === URI.record: return w + getWeight(x.def) + case x.tag === URI.union: return w + x.def.reduce(aggregateWeights, 0) + case x.tag === URI.intersect: return w + x.def.reduce(aggregateWeights, 0) + case x.tag === URI.tuple: return w + x.def.reduce(aggregateWeights, 0) + case x.tag === URI.object: return w + x.def.map(([, v]) => v).reduce(aggregateWeights, 0) + } } -export let check = fn.flow( - Weighted.sort, - Weighted.fold(Jit.check), +/** + * Binding the element's index to the element itself is a hack to make sure + * we preserve the original order of the tuple, even while sorting + */ +let bindPreSortIndices: (x: T[]) => T[] = (x) => { + for (let ix = 0, len = x.length; ix < len; ix++) + (x[ix] as any).preSortIndex = ix + return x +} + +export let sort: (schema: t.Schema) => IR = fn.flow( + t.fold((x) => + x.tag !== URI.object ? x + : t.object.def( + Object.entries(x.def), + undefined, + Array.of().concat(x.opt), + ) + ), + fold((x) => { + switch (true) { + default: return fn.exhaustive(x) + case t.isNullary(x): return x + case t.isBoundable(x): return x + case x.tag === URI.eq: return x + case x.tag === URI.optional: return t.optional.def(x.def) + case x.tag === URI.array: return t.array.def(x.def) + case x.tag === URI.record: return t.record.def(x.def) + case x.tag === URI.union: return t.union.def(x.def.sort(weightComparator)) + case x.tag === URI.intersect: return t.intersect.def([...x.def].sort(weightComparator)) + case x.tag === URI.tuple: return t.tuple.def(bindPreSortIndices(x.def).sort(weightComparator)) + case x.tag === URI.object: return t.object.def( + x.def.sort(([, l], [, r]) => weightComparator(l, r)), + undefined, + x.opt, + ) + } + }), ) -export let jitBody = (schema: t.Schema) => { - let BODY = check(schema).trim() +export function buildFunctionBody(schema: t.Schema): string { + let BODY = fn.pipe( + sort(schema), + fold(interpreter), + ).trim() + if (BODY.startsWith('(') && BODY.endsWith(')')) void (BODY = BODY.slice(1, -1)) @@ -297,37 +308,55 @@ export let jitBody = (schema: t.Schema) => { let OPEN = SINGLE_LINE ? '' : `(\r${' '.repeat(4)}` let CLOSE = SINGLE_LINE ? '' : `\r${' '.repeat(2)})` - return OPEN + BODY + CLOSE + return '' + + OPEN + + BODY + + CLOSE } -export let jit = (schema: t.Schema) => ` +export let generate = (schema: t.Schema): string => ` function check(value) { - return ${jitBody(schema)} + return ${buildFunctionBody(schema)} } -` - .trim() -export let parseBody = (schema: t.Schema) => { - return ` +`.trim() -function parse(value) { - function check(value) { - return ( - ${jitBody(schema)} - ) - } - if (check(value)) return value - else throw Error("invalid input") -} -` - .trim() +export let generateParser = (schema: t.Schema): string => ` + +function check(value) { + return ${buildFunctionBody(schema)} } +if (check(value)) return value +else throw Error("invalid input") + +`.trim() + export function compile(schema: S): ((x: S['_type'] | T.Unknown) => x is S['_type']) -export function compile(schema: t.Schema): Function { return globalThis.Function('value', 'return ' + jitBody(schema)) } +export function compile(schema: t.Schema): Function { + return globalThis.Function( + 'value', + 'return ' + buildFunctionBody(schema) + ) +} + export function compileParser(schema: S): ((x: S['_type'] | T.Unknown) => S['_type']) -export function compileParser(schema: t.Schema): Function { return globalThis.Function('value', 'return ' + parseBody(schema)) } +export function compileParser(schema: t.Schema): Function { + return globalThis.Function( + 'value', + 'return' + ` + +function check(value) { + return ${buildFunctionBody(schema)} +} +if (check(value)) return value +else throw Error("invalid input") + +` + .trim() + ) +} diff --git a/packages/schema-jit-compiler/src/json.ts b/packages/schema-jit-compiler/src/json.ts index c927b529..fdd48b25 100644 --- a/packages/schema-jit-compiler/src/json.ts +++ b/packages/schema-jit-compiler/src/json.ts @@ -1,8 +1,9 @@ import type * as T from '@traversable/registry' -import { fn, isValidIdentifier, Object_entries, Object_values, URI } from '@traversable/registry' +import { escape, fn, isValidIdentifier, Object_entries, Object_values, URI } from '@traversable/registry' import { Json } from '@traversable/json' -import type { Index as Ix } from './functor.js' +import type { Index as Ix } from './shared.js' +import { buildContext } from './shared.js' export let isScalar = Json.isScalar export let isArray = Json.isArray @@ -18,22 +19,20 @@ export let WeightByType = { object: 256, } as const -export type Any = import('@traversable/json').Json -export type Unary = import('@traversable/json').Unary -export interface IRFree extends T.HKT { [-1]: IR } +export interface Free extends T.HKT { [-1]: IR } export type IR = | { tag: URI.bottom, def: Json.Scalar } | { tag: URI.array, def: T[] } | { tag: URI.object, def: [k: string, v: T][] } -export type IRFixpoint = +export type Fixpoint = | { tag: URI.bottom, def: Json.Scalar } - | { tag: URI.array, def: IRFixpoint[] } - | { tag: URI.object, def: [k: string, v: IRFixpoint][] } + | { tag: URI.array, def: Fixpoint[] } + | { tag: URI.object, def: [k: string, v: Fixpoint][] } export type Index = Omit -export type Algebra = T.IndexedAlgebra +export type Algebra = T.IndexedAlgebra export let defaultIndex = { dataPath: [], @@ -44,7 +43,7 @@ export let defaultIndex = { } satisfies Index let map - : T.Functor['map'] + : T.Functor['map'] = (f) => (xs) => { switch (true) { default: return fn.exhaustive(xs) @@ -57,7 +56,7 @@ let map } } -export let Functor: T.Functor.Ix = { +export let Functor: T.Functor.Ix = { map, mapWithIndex(f) { return function mapFn(xs, ix) { @@ -87,35 +86,18 @@ export let Functor: T.Functor.Ix = { }, } -export let fold - : (algebra: T.IndexedAlgebra) => (json: IR, ix?: Index) => T - = (algebra) => (json, index = defaultIndex) => fn.cataIx(Functor)(algebra)(json as /* FIXME */ never, index) - -export let toIR = Json.fold((x) => { - switch (true) { - default: return fn.exhaustive(x) - case x === undefined: - case x === null: - case typeof x === 'boolean': - case typeof x === 'number': - case typeof x === 'string': return { tag: URI.bottom, def: x } - case Json.isArray(x): return { tag: URI.array, def: [...x] } - case Json.isObject(x): return { tag: URI.object, def: Object_entries(x) } - } -}) - -let aggregateWeights - : (acc: number, curr: T.Param) => number - = (acc, curr) => acc + getWeight(curr) - +export function fold(algebra: T.IndexedAlgebra): (json: IR, ix?: Index) => T +export function fold(algebra: T.IndexedAlgebra) { + return (json: Fixpoint, index = defaultIndex) => fn.cataIx(Functor)(algebra)(json, index) +} -export let weightComparator: T.Comparator = (l, r) => { +let comparator: T.Comparator = (l, r) => { let lw = getWeight(l) let rw = getWeight(r) return lw < rw ? -1 : rw < lw ? +1 : 0 } -export let getWeight = (x: Any): number => { +export let getWeight = (x: Json): number => { switch (true) { default: return fn.exhaustive(x) case x === undefined: return WeightByType.undefined @@ -123,21 +105,79 @@ export let getWeight = (x: Any): number => { case typeof x === 'boolean': return WeightByType.boolean case typeof x === 'number': return WeightByType.number case typeof x === 'string': return WeightByType.string - case Json.isArray(x): return WeightByType.array + x.reduce(aggregateWeights, 0) - case Json.isObject(x): return WeightByType.object + Object_values(x).reduce(aggregateWeights, 0) + case Json.isArray(x): return WeightByType.array + x.reduce((acc: number, cur) => acc + getWeight(cur), 0) + case Json.isObject(x): return WeightByType.object + Object_values(x).reduce((acc: number, cur) => acc + getWeight(cur), 0) } } -let sortAlgebra: Algebra = (x) => { +export let sort = fn.flow( + Json.fold((x) => { + switch (true) { + default: return fn.exhaustive(x) + case x === undefined: + case x === null: + case typeof x === 'boolean': + case typeof x === 'number': + case typeof x === 'string': return { tag: URI.bottom, def: x } + case Json.isArray(x): return { tag: URI.array, def: [...x] } + case Json.isObject(x): return { tag: URI.object, def: Object_entries(x) } + } + }), + fold((x) => { + switch (true) { + default: return fn.exhaustive(x) + case x.tag === URI.bottom: return x + case x.tag === URI.array: return { tag: URI.array, def: x.def.sort(comparator) } + case x.tag === URI.object: return { tag: URI.object, def: x.def.sort(([, l], [, r]) => comparator(l, r)) } + } + }), +) + +export function interpreter(x: IR>, ix: Index): IR +export function interpreter(x: IR>, ix: Index): { + tag: URI.bottom | URI.array | URI.object + def: unknown +} { + let { VAR, join } = buildContext(ix) switch (true) { default: return fn.exhaustive(x) - case x.tag === URI.bottom: return x - case x.tag === URI.array: return { tag: URI.array, def: x.def.sort(weightComparator) } - case x.tag === URI.object: return { tag: URI.object, def: x.def.sort(([, l], [, r]) => weightComparator(l, r)) } + case x.tag === URI.bottom: { + let BODY = VAR + ' === ' + switch (true) { + default: return fn.exhaustive(x.def) + case x.def === null: BODY += 'null'; break + case x.def === undefined: BODY += 'undefined'; break + case x.def === true: BODY += 'true'; break + case x.def === false: BODY += 'false'; break + case x.def === 0: BODY += 1 / x.def === Number.NEGATIVE_INFINITY ? '-0' : '+0'; break + case typeof x.def === 'string': BODY += `"${escape(x.def)}"`; break + case typeof x.def === 'number': BODY += String(x.def); break + } + return { + tag: URI.bottom, + def: BODY, + } + } + case x.tag === URI.array: return { + tag: URI.array, + def: '' + + `Array.isArray(${VAR}) && ` + + `${VAR}.length === ${x.def.length}` + + (x.def.length === 0 ? '' : x.def.map((v, i) => i === 0 ? join(0) + v.def : v.def).join(join(0))), + } + case x.tag === URI.object: return { + tag: URI.object, + def: '' + + `!!${VAR} && typeof ${VAR} === "object" && !Array.isArray(${VAR})` + + (x.def.length === 0 ? '' : x.def.map(([, v], i) => i === 0 ? join(0) + v.def : v.def).join(join(0))), + } } } -export let sort = fn.flow( - toIR, - fold(sortAlgebra), -) +export function generate(json: Json, index?: Index): string +export function generate(json: Json, index?: Index) { + return fn.pipe( + sort(json), + (sorted) => fold(interpreter)(sorted, index).def, + ) +} diff --git a/packages/schema-jit-compiler/src/shared.ts b/packages/schema-jit-compiler/src/shared.ts new file mode 100644 index 00000000..d47991e4 --- /dev/null +++ b/packages/schema-jit-compiler/src/shared.ts @@ -0,0 +1,71 @@ +import type * as T from '@traversable/registry' +import { isValidIdentifier, parseKey } from '@traversable/registry' +import { t } from '@traversable/schema-core' + +import type { Index } from './functor.js' + +export type F = + | t.Leaf + | t.eq + | t.array + | t.record + | t.optional + | t.union + | t.intersect + | t.tuple + | t.object<[k: string, v: T][]> + +export type Fixpoint = + | t.Leaf + | t.eq + | t.array + | t.record + | t.optional + | t.union + | t.intersect + | t.tuple + | t.object<[k: string, v: Fixpoint][]> + +export interface Free extends T.HKT { [-1]: F } + +export type Context = { + VAR: string + // RETURN: string + // TABSTOP: string + // JOIN: string + indent(numberOfSpaces: number): string + dedent(numberOfSpaces: number): string + join(numberOfSpaces: number): string +} + +export function buildContext(ix: T.Require): Context { + let VAR = ix.varName + let indent = (numberOfSpaces: number) => `\r${' '.repeat(Math.max(ix.offset + numberOfSpaces, 0))}` + let dedent = (numberOfSpaces: number) => `\r${' '.repeat(Math.max(ix.offset - numberOfSpaces, 0))}` + let join = (numberOfSpaces: number) => indent(numberOfSpaces) + '&& ' + // let JOIN = join(2) + // let RETURN = indent(2) + // let TABSTOP = indent(4) + return { dedent, indent, join, VAR } +} + +export function keyAccessor(key: keyof any | undefined, $: Index) { + return typeof key === 'string' ? isValidIdentifier(key) ? $.isOptional + ? `?.${key}` + : `.${key}` + : `[${parseKey(key)}]` + : '' +} + +/** + * Reading `x` to access the "preSortIndex" is a hack to make sure + * we preserve the original order of the tuple, even while sorting + */ +export function indexAccessor(index: keyof any | undefined, $: { isOptional?: boolean }, x?: any) { + return 'preSortIndex' in x + ? $.isOptional ? `?.[${x.preSortIndex}]` : `[${x.preSortIndex}]` + : typeof index === 'number' ? $.isOptional + ? `?.[${index}]` + : `[${index}]` + : '' +} diff --git a/packages/schema-jit-compiler/src/sort.ts b/packages/schema-jit-compiler/src/sort.ts deleted file mode 100644 index 162923fd..00000000 --- a/packages/schema-jit-compiler/src/sort.ts +++ /dev/null @@ -1,233 +0,0 @@ -import type * as T from '@traversable/registry' -import { fn, symbol, typeName, URI } from '@traversable/registry' -import { t } from '@traversable/schema-core' - -import * as F from './functor.js' - -export let WeightByTypeName = { - never: 0, - any: 10, - unknown: 20, - void: 30, - undefined: 40, - null: 50, - symbol: 60, - boolean: 70, - integer: 80, - bigint: 90, - number: 100, - string: 110, - optional: 120, - intersect: 130, - union: 140, - tuple: 150, - object: 160, - array: 170, - record: 180, - eq: 190, -} as const - -export interface Free extends T.HKT { [-1]: IR } -export type Algebra = T.IndexedAlgebra - -export type IR = - | t.Leaf - | t.eq - | t.array - | t.record - | t.optional - | t.union - | t.intersect - | t.tuple - | t.object<[k: string, T][]> - - -export let map: T.Functor['map'] = (f) => { - return (x) => { - switch (true) { - default: return fn.exhaustive(x) - case t.isNullary(x): return x - case t.isBoundable(x): return x - case x.tag === URI.eq: return t.eq.def(x.def as never) - case x.tag === URI.optional: return t.optional.def(f(x.def)) - case x.tag === URI.array: return t.array.def(f(x.def)) - case x.tag === URI.record: return t.record.def(f(x.def)) - case x.tag === URI.union: return t.union.def(fn.map(x.def, f)) - case x.tag === URI.intersect: return t.intersect.def(fn.map(x.def, f)) - case x.tag === URI.tuple: return t.tuple.def(fn.map(x.def, f)) - case x.tag === URI.object: return t.object.def( - fn.map(x.def, ([k, v]) => [k, f(v)] satisfies [any, any]), - undefined, - x.opt, - ) - } - } -} - -export let Functor: T.Functor.Ix = { - map, - mapWithIndex(f) { - return (xs, ix) => { - switch (true) { - default: return fn.exhaustive(xs) - case t.isNullary(xs): return xs - case t.isBoundable(xs): return xs - case xs.tag === URI.eq: return xs as never - case xs.tag === URI.optional: return t.optional.def(f(xs.def, { - dataPath: ix.dataPath, - isOptional: true, - isRoot: false, - offset: ix.offset + 2, - schemaPath: [...ix.schemaPath, symbol.optional], - siblingCount: 0, - varName: ix.varName, - })) - case xs.tag === URI.array: return t.array.def(f(xs.def, { - dataPath: ix.dataPath, - isOptional: ix.isOptional, - isRoot: false, - offset: ix.offset + 2, - schemaPath: [...ix.schemaPath, symbol.array], - siblingCount: 0, - varName: 'value', - })) - case xs.tag === URI.record: return t.record.def(f(xs.def, { - dataPath: ix.dataPath, - isOptional: ix.isOptional, - isRoot: false, - offset: ix.offset + 2, - schemaPath: [...ix.schemaPath, symbol.array], - siblingCount: 0, - varName: 'value', - })) - case xs.tag === URI.union: return t.union.def(fn.map(xs.def, (x, i) => f(x, { - dataPath: ix.dataPath, - isOptional: ix.isOptional, - isRoot: false, - offset: ix.offset + 2, - schemaPath: [...ix.schemaPath, i], - siblingCount: Math.max(xs.def.length - 1, 0), - varName: ix.varName, - }))) - case xs.tag === URI.intersect: return t.intersect.def(fn.map(xs.def, (x, i) => f(x, { - dataPath: ix.dataPath, - isOptional: ix.isOptional, - isRoot: false, - offset: ix.offset + 2, - schemaPath: [...ix.schemaPath, i], - siblingCount: Math.max(xs.def.length - 1, 0), - varName: ix.varName, - }))) - case xs.tag === URI.tuple: - return t.tuple.def(fn.map(xs.def, (x, i) => f(x, { - dataPath: [...ix.dataPath, i], - isOptional: ix.isOptional, - isRoot: false, - offset: ix.offset + 2, - schemaPath: [...ix.schemaPath, i], - siblingCount: Math.max(xs.def.length - 1, 0), - /** - * Passing `x` to `indexAccessor` is a hack to make sure - * we preserve the original order of the tuple while we're - * applying a sorting optimization - */ - varName: ix.varName + F.indexAccessor(i, ix, x), - }))) - - case xs.tag === URI.object: { - return t.object.def( - fn.map(xs.def, ([k, v]) => [k, f(v, { - dataPath: [...ix.dataPath, k], - isOptional: ix.isOptional, - isRoot: false, - offset: ix.offset + 2, - schemaPath: [...ix.schemaPath, k], - siblingCount: Math.max(Object.keys(xs.def).length - 1, 0), - varName: ix.varName + F.keyAccessor(k, ix), - })] satisfies [any, any]), - undefined, - xs.opt, - ) - } - } - } - }, -} - -export let fold = (algebra: Algebra) => (x: IR) => fn.cataIx(Functor)(algebra)(x, F.defaultIndex) -export let print = fold((x) => t.isNullary(x) ? x.tag : t.isBoundable(x) ? x.tag : x.def) -export let toIR = t.fold( - (x) => x.tag !== URI.object ? x : t.object.def( - Object.entries(x.def), - undefined, - Array.of().concat(x.opt), - ) -) - -let aggregateWeights - : (acc: number, curr: t.Schema) => number - = (acc, curr) => Math.max(acc, getWeight(curr)) - -let weightComparator: T.Comparator = (l, r) => { - let lw = getWeight(l) - let rw = getWeight(r) - return lw < rw ? -1 : rw < lw ? +1 : 0 -} - -function getWeight(x: IR): number -function getWeight(x: t.Schema): number -function getWeight(x: IR): number { - let w = WeightByTypeName[typeName(x)] - switch (true) { - default: return fn.exhaustive(x) - case t.isNullary(x): return w - case t.isBoundable(x): return w - case x.tag === URI.eq: return w - case x.tag === URI.optional: return w + getWeight(x.def) - case x.tag === URI.array: return w + getWeight(x.def) - case x.tag === URI.record: return w + getWeight(x.def) - case x.tag === URI.union: return w + x.def.reduce(aggregateWeights, 0) - case x.tag === URI.intersect: return w + x.def.reduce(aggregateWeights, 0) - case x.tag === URI.tuple: return w + x.def.reduce(aggregateWeights, 0) - case x.tag === URI.object: return w + x.def.map(([, v]) => v).reduce(aggregateWeights, 0) - } -} - -/** - * Binding the element's index to the element itself is a hack to make sure - * we preserve the original order of the tuple, even while sorting - */ -let bindPreSortIndices = (x: T[]) => { - for (let ix = 0, len = x.length; ix < len; ix++) { - let def = x[ix] - ; (def as any).preSortIndex = ix - } - return x -} - -let sortAlgebra: Algebra = (x) => { - switch (true) { - default: return fn.exhaustive(x) - case t.isNullary(x): return x - case t.isBoundable(x): return x - case x.tag === URI.eq: return x - case x.tag === URI.optional: return t.optional.def(x.def) - case x.tag === URI.array: return t.array.def(x.def) - case x.tag === URI.record: return t.record.def(x.def) - case x.tag === URI.union: return t.union.def(x.def.sort(weightComparator)) - case x.tag === URI.intersect: return t.intersect.def([...x.def].sort(weightComparator)) - case x.tag === URI.tuple: return ( - bindPreSortIndices(x.def), - t.tuple.def(x.def.sort(weightComparator)) - ) - case x.tag === URI.object: return t.object.def( - x.def.sort(([, l], [, r]) => weightComparator(l, r)), - undefined, - x.opt, - ) - } -} - -export let sort - : (schema: t.Schema) => IR - = fn.flow(toIR, fold(sortAlgebra)) diff --git a/packages/schema-jit-compiler/test/TODO.ts b/packages/schema-jit-compiler/test/TODO.ts index d08cf0a7..bc2ca4b3 100644 --- a/packages/schema-jit-compiler/test/TODO.ts +++ b/packages/schema-jit-compiler/test/TODO.ts @@ -1,106 +1,50 @@ import { fc } from '@fast-check/vitest' - -import type { Bounds, TypeName } from '@traversable/registry' -import { fn, pick, typeName, URI } from '@traversable/registry' +import { fn, URI } from '@traversable/registry' import { t } from '@traversable/schema-core' -import type { BoundableTag, NullaryTag, UnaryTag } from '@traversable/schema-core' - -type NullaryType = TypeName -type BoundableType = TypeName -type UnaryType = TypeName -type GetType = never | t.Catalog[K]['_type'] -type GetBounds = never | Bounds -type GetInput = t.Catalog.Unary>[K] -type GetOutput[K]['_type']> = t.unknown extends T ? unknown : T -type BoundableKeys = - | 'minLength' - | 'maxLength' - | 'minimum' - | 'maximum' - -/** @internal */ -let empty = fc.constant(void 0 as never) - -type Nullary = never | { [K in NullaryType]: fc.Arbitrary> } -let Nullary = { - never: empty, - any: fc.anything() as fc.Arbitrary, - unknown: fc.anything(), - void: empty as fc.Arbitrary, - null: fc.constant(null), - undefined: fc.constant(undefined), - symbol: fc.string().map((_) => Symbol(_)), - boolean: fc.boolean(), -} as const satisfies Nullary - -type Boundable = never | { [K in BoundableType]: (bounds?: GetBounds) => fc.Arbitrary> } -let Boundable = { - integer: (b) => fc.integer({ min: b?.gte, max: b?.lte }), - bigint: (b) => fc.bigInt({ min: b?.gte, max: b?.lte }), - string: (b) => fc.string({ minLength: b?.gte, maxLength: b?.lte }), - number: (b) => fc.float({ - min: b?.gte, - max: b?.lte, - ...t.number(b?.gt) && { minExcluded: true, min: b.gt }, - ...t.number(b?.lt) && { maxExcluded: true, max: b.lt }, - }), -} as const satisfies Boundable - -type Unary = never | { [K in UnaryType]: (x: GetInput) => fc.Arbitrary> } -let Unary = { - eq: (x) => fc.constant(x.def), - optional: (x) => fc.option(x.def, { nil: void 0 }), - array: (x) => fc.array(x.def), - record: (x) => fc.dictionary(fc.string(), x.def), - union: (xs) => fc.oneof(...xs.def), - tuple: (xs) => fc.tuple(...xs.def), - intersect: (xs) => fc.tuple(...xs.def).map((arbs) => arbs.reduce((acc: {}, cur) => cur == null ? acc : Object.assign(acc, cur), {})), - object: (xs) => fc.record(xs.def, { ...[xs.opt].concat().length > 0 && { requiredKeys: Array.of().concat(xs.req) } }), -} as const satisfies Unary - -function getBounds(schema: S): Pick -function getBounds(schema: S): Pick -function getBounds(schema: S): Pick -function getBounds(schema: S): Pick -function getBounds(schema: S): Bounds -function getBounds(schema: t.Boundable) { - return pick(schema, ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum']) -} -function fromSchemaAlgebra(options?: fromSchema.Options): t.Functor.Algebra> -function fromSchemaAlgebra(_?: fromSchema.Options): t.Functor.Algebra> { - return (x) => { - switch (true) { - default: return fn.exhaustive(x) - case t.isNullary(x): return Nullary[typeName(x)] - case t.isBoundable(x): return Boundable[typeName(x)](getBounds(x)) - case x.tag === URI.eq: return Unary.eq(x) - case x.tag === URI.optional: return Unary.optional(x) - case x.tag === URI.array: return Unary.array(x) - case x.tag === URI.record: return Unary.record(x) - case x.tag === URI.union: return Unary.union(x) - case x.tag === URI.intersect: return Unary.intersect(x) - case x.tag === URI.tuple: return Unary.tuple(x) - case x.tag === URI.object: return Unary.object(x) - } - } -} - -export let defaultOptions = { - -} satisfies Required - -declare namespace fromSchema { - type Options = { - - } -} +export let defaultOptions = {} satisfies Required +declare namespace fromSchema { type Options = {} } /** * ## {@link fromSchema `Arbitrary.fromSchema`} */ export let fromSchema : (schema: S, options?: fromSchema.Options) => fc.Arbitrary - = (schema, options) => t.fold(fromSchemaAlgebra(options))(schema) - - ; (fromSchema as typeof fromSchema & { defaultOptions?: typeof defaultOptions }).defaultOptions = defaultOptions + = (schema) => t.fold>( + (x) => { + switch (true) { + default: return fn.exhaustive(x) + case x.tag === URI.never: return fc.constant(void 0) + case x.tag === URI.any: return fc.anything() + case x.tag === URI.unknown: return fc.anything() + case x.tag === URI.void: return fc.constant(void 0) + case x.tag === URI.null: return fc.constant(null) + case x.tag === URI.undefined: return fc.constant(undefined) + case x.tag === URI.symbol: return fc.string().map((_) => Symbol(_)) + case x.tag === URI.boolean: return fc.boolean() + case x.tag === URI.integer: return fc.integer({ min: x.minimum, max: x.maximum }) + case x.tag === URI.bigint: return fc.bigInt({ min: x.minimum, max: x.maximum }) + case x.tag === URI.string: return fc.string({ minLength: x.minLength, maxLength: x.maxLength }) + case x.tag === URI.number: return fc.float({ + min: t.number(x.exclusiveMinimum) ? x.exclusiveMinimum : x.minimum, + max: t.number(x.exclusiveMaximum) ? x.exclusiveMaximum : x.minimum, + minExcluded: t.number(x.exclusiveMinimum), + maxExcluded: t.number(x.exclusiveMaximum), + }) + case x.tag === URI.eq: return fc.constant(x.def) + case x.tag === URI.optional: return fc.option(x.def, { nil: void 0 }) + case x.tag === URI.array: return fc.array(x.def) + case x.tag === URI.record: return fc.dictionary(fc.string(), x.def) + case x.tag === URI.union: return fc.oneof(...x.def) + case x.tag === URI.tuple: return fc.tuple(...x.def) + case x.tag === URI.intersect: return fc.tuple(...x.def).map((xs) => xs.reduce<{}>( + (acc, cur) => cur == null ? acc : Object.assign(acc, cur), {} + )) + case x.tag === URI.object: { + let requiredKeys = Array.of().concat(x.req) + let optionalKeys = Array.of().concat(x.opt) + return fc.record(x.def, { ...optionalKeys.length > 0 && { requiredKeys } }) + } + } + } + )(schema) diff --git a/packages/schema-jit-compiler/test/functor.test.ts b/packages/schema-jit-compiler/test/functor.test.ts deleted file mode 100644 index 06d56615..00000000 --- a/packages/schema-jit-compiler/test/functor.test.ts +++ /dev/null @@ -1,9 +0,0 @@ -import * as vi from 'vitest' - -import { t, configure } from '@traversable/schema-core' -import { Functor, jit } from '@traversable/schema-jit-compiler' - -vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () => { - -}) - diff --git a/packages/schema-jit-compiler/test/jit.test.ts b/packages/schema-jit-compiler/test/jit.test.ts index 84cf6e21..409ba99c 100644 --- a/packages/schema-jit-compiler/test/jit.test.ts +++ b/packages/schema-jit-compiler/test/jit.test.ts @@ -1,15 +1,15 @@ import * as vi from 'vitest' import { fc, test } from '@fast-check/vitest' - import { Seed } from '@traversable/schema-seed' import { t, configure } from '@traversable/schema-core' -import { compile, jit, jitJson } from '@traversable/schema-jit-compiler' + +import { Jit } from '@traversable/schema-jit-compiler' import * as Arbitrary from './TODO.js' -vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: property tests (randomly generated inputs)', () => { - let schema = t.object({ +vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: Jit.compile w/ randomly generated data', () => { + let hardcodedSchema = t.object({ "#1C": t.object({ twoC: t.intersect( t.object({ @@ -44,131 +44,153 @@ vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: ), }) - let check = compile(schema) - let arbitrary = Arbitrary.fromSchema(schema) + let check = Jit.compile(hardcodedSchema) - test.prop([arbitrary], { + test.prop([Arbitrary.fromSchema(hardcodedSchema)], { // numRuns: 10_000, endOnFailure: true, - })('〖⛳️〗› ❲jit.check❳: succeeds with randomly generated, valid input', (data) => { - try { - vi.assert.isTrue(check(data)) - } catch (e) { - let jitted = jit(schema) - + })('〖⛳️〗› ❲Jit.compile❳: randomly generated data', (_) => { + try { vi.assert.isTrue(check(_)) } + catch (e) { + let generated = Jit.generate(hardcodedSchema) + console.group('\r\n ===== Jit.compile property test failed ===== \r\n') console.error() console.error('Check for valid, randomly generated data failed') - console.error('Schema:\r\n\n' + jitted + '\r\n\n') - console.error('Input:\r\n\n' + JSON.stringify(data, null, 2) + '\r\n\n') - + console.error('Schema:\r\n\n' + generated + '\r\n\n') + console.error('Input:\r\n\n' + JSON.stringify(_, null, 2) + '\r\n\n') + console.groupEnd() vi.assert.fail(t.has('message', t.string)(e) ? e.message : JSON.stringify(e, null, 2)) } }) }) -vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: property tests (random generated schemas)', () => { - let seed = fc.letrec(Seed.seed()) - test.prop([seed.tree], { + +vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: Jit.compile w/ randomly generated schemas', () => { + test.prop([fc.letrec(Seed.seed()).tree], { endOnFailure: true, numRuns: 10, // numRuns: 10_000, })( - 'property tests (random generated schemas)', (seed) => { + '〖⛳️〗› ❲Jit.compile❳: randomly generated schema', (seed) => { let schema = Seed.toSchema(seed) - let check = compile(schema) + let check = Jit.compile(schema) let arbitrary = Arbitrary.fromSchema(schema) let inputs = fc.sample(arbitrary, 100) for (let input of inputs) { - try { - vi.assert.isTrue(check(input)) - } catch (e) { - console.error(t.has('message', t.string)(e) ? e.message : JSON.stringify(e, null, 2)) - + try { vi.assert.isTrue(check(input)) } + catch (e) { + let generated = Jit.generate(schema) + console.group('\r\n ===== Jit.compile property test failed ===== \r\n') + console.error() + console.error('Check for valid, randomly generated data failed') + console.error('Schema:\r\n\n' + generated + '\r\n\n') + console.error('Input:\r\n\n' + JSON.stringify(input, null, 2) + '\r\n\n') + console.groupEnd() + vi.assert.fail(t.has('message', t.string)(e) ? e.message : JSON.stringify(e, null, 2)) } } - } ) }) -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: compile(...)', () => { - vi.describe('〖⛳️〗‹‹ ❲compile❳: eq', () => { - let check = compile( +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: Jit.compile', () => { + vi.describe('〖⛳️〗‹‹ ❲Jit.compile❳: eq', () => { + let check = Jit.compile( t.eq({ a: false, }) ) vi.test.concurrent.for([ - /* FAILURE */ {}, { a: true }, - ])('t.eq check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + ])( + '〖⛳️〗› ❲t.eq❳: check fails with bad input (index %#)', + (_) => vi.assert.isFalse(check(_)) + ) vi.test.concurrent.for([ - /* SUCCESS */ { a: false }, - ])('t.eq check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + ])( + '〖⛳️〗› ❲t.eq❳: check succeeds with valid input (index %#)', + (_) => vi.assert.isTrue(check(_)) + ) }) - vi.describe('〖⛳️〗‹‹ ❲compile❳: array', () => { - let check = compile( + + vi.describe('〖⛳️〗‹‹ ❲Jit.compile❳: array', () => { + let check = Jit.compile( t.array(t.boolean) ) vi.test.concurrent.for([ - /* FAILURE */ [1], - ])('t.array check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + ])( + '〖⛳️〗› ❲t.array❳: check fails with bad input (index %#)', + (_) => vi.assert.isFalse(check(_)) + ) vi.test.concurrent.for([ - /* SUCCESS */ [], [Math.random() > 0.5], - ])('t.array check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + ])( + '〖⛳️〗› ❲t.array❳: check succeeds with valid input (index %#)', + (_) => vi.assert.isTrue(check(_)) + ) }) - vi.describe('〖⛳️〗‹‹ ❲compile❳: record', () => { - let check = compile( + + vi.describe('〖⛳️〗‹‹ ❲Jit.compile❳: record', () => { + let check = Jit.compile( t.record(t.boolean) ) vi.test.concurrent.for([ - /* FAILURE */ [], { a: 0 }, - ])('t.record check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + ])( + '〖⛳️〗› ❲t.record❳: check fails with bad input (index %#)', + (_) => vi.assert.isFalse(check(_)) + ) vi.test.concurrent.for([ - /* SUCCESS */ {}, { a: false }, - ])('t.record check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + ])( + '〖⛳️〗› ❲t.record❳: check succeeds with valid input (index %#)', + (_) => vi.assert.isTrue(check(_)) + ) }) - vi.describe('〖⛳️〗‹‹ ❲compile❳: optional', () => { - let check = compile( + + vi.describe('〖⛳️〗‹‹ ❲Jit.compile❳: optional', () => { + let check = Jit.compile( t.object({ a: t.optional(t.boolean), }) ) vi.test.concurrent.for([ - /* FAILURE */ { a: 0 }, - ])('t.optional check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + ])( + '〖⛳️〗› ❲t.optional❳: check fails with bad input (index %#)', + (_) => vi.assert.isFalse(check(_)) + ) vi.test.concurrent.for([ - /* SUCCESS */ {}, { a: false }, - ])('t.optional check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + ])( + '〖⛳️〗› ❲t.optional❳: check succeeds with valid input (index %#)', + (_) => vi.assert.isTrue(check(_)) + ) }) - vi.describe('〖⛳️〗‹‹ ❲compile❳: tuple', () => { - let check = compile( + + vi.describe('〖⛳️〗‹‹ ❲Jit.compile❳: tuple', () => { + let check = Jit.compile( t.tuple( t.string, t.number, @@ -176,19 +198,24 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: compi ) vi.test.concurrent.for([ - /* FAILURE */ [], [0, ''], - ])('t.tuple check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + ])( + '〖⛳️〗› ❲t.tuple❳: check fails with bad input (index %#)', + (_) => vi.assert.isFalse(check(_)) + ) vi.test.concurrent.for([ - /* SUCCESS */ ['', 0], - ])('t.tuple check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + ])( + '〖⛳️〗› ❲t.tuple❳: check succeeds with valid input (index %#)', + (_) => vi.assert.isTrue(check(_)) + ) }) - vi.describe('〖⛳️〗‹‹ ❲compile❳: union', () => { - let check = compile( + + vi.describe('〖⛳️〗‹‹ ❲Jit.compile❳: union', () => { + let check = Jit.compile( t.union( t.string, t.number, @@ -196,19 +223,24 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: compi ) vi.test.concurrent.for([ - /* FAILURE */ false, - ])('t.union check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + ])( + '〖⛳️〗› ❲t.union❳: check fails with bad input (index %#)', + (_) => vi.assert.isFalse(check(_)) + ) vi.test.concurrent.for([ - /* SUCCESS */ '', 0, - ])('t.union check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + ])( + '〖⛳️〗› ❲t.union❳: check succeeds with valid input (index %#)', + (_) => vi.assert.isTrue(check(_)) + ) }) - vi.describe('〖⛳️〗‹‹ ❲compile❳: intersect', () => { - let check = compile( + + vi.describe('〖⛳️〗‹‹ ❲Jit.compile❳: intersect', () => { + let check = Jit.compile( t.intersect( t.object({ a: t.boolean, @@ -220,353 +252,56 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: compi ) vi.test.concurrent.for([ - /* FAILURE */ {}, { a: false }, { b: 0 }, { a: false, b: '' }, { a: '', b: 0 }, - ])('t.intersect check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + ])( + '〖⛳️〗› ❲t.intersect❳: check fails with bad input (index %#)', + (_) => vi.assert.isFalse(check(_)) + ) vi.test.concurrent.for([ - /* SUCCESS */ { a: false, b: 0 }, - ])('t.intersect check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) + ])( + '〖⛳️〗› ❲t.intersect❳: check succeeds with valid input (index %#)', + (_) => vi.assert.isTrue(check(_)) + ) }) - vi.describe('〖⛳️〗‹‹ ❲compile❳: object', () => { - let check = compile( + + vi.describe('〖⛳️〗‹‹ ❲Jit.compile❳: object', () => { + let check = Jit.compile( t.object({ a: t.boolean, }) ) vi.test.concurrent.for([ - /* FAILURE */ {}, { a: 0 }, { b: false }, - ])('t.object check fails with bad input (index %#)', (input) => vi.assert.isFalse(check(input))) + ])( + '〖⛳️〗› ❲t.object❳: check fails with bad input (index %#)', + (_) => vi.assert.isFalse(check(_)) + ) vi.test.concurrent.for([ - /* SUCCESS */ { a: false }, - ])('t.object check succeeds with valid input (index %#)', (input) => vi.assert.isTrue(check(input))) - }) - -}) - - -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: jitJson', () => { - - vi.it('〖⛳️〗› ❲jitJson❳: bad input', () => { - /* @ts-expect-error */ - vi.assert.throws(() => jitJson(Symbol())) - - /* @ts-expect-error */ - vi.assert.throws(() => jitJson(1n)) - }) - - vi.it('〖⛳️〗› ❲jitJson❳: null', () => { - vi.expect(jitJson( - null - )).toMatchInlineSnapshot - - (`"value === null"`) - }) - - vi.it('〖⛳️〗› ❲jitJson❳: undefined', () => { - vi.expect(jitJson( - undefined - )).toMatchInlineSnapshot - (`"value === undefined"`) - }) - - vi.it('〖⛳️〗› ❲jitJson❳: booleans', () => { - vi.expect(jitJson( - false - )).toMatchInlineSnapshot - (`"value === false"`) - - vi.expect(jitJson( - true - )).toMatchInlineSnapshot - (`"value === true"`) - }) - - vi.it('〖⛳️〗› ❲jitJson❳: numbers', () => { - vi.expect(jitJson( - Number.MIN_SAFE_INTEGER - )).toMatchInlineSnapshot - (`"value === -9007199254740991"`) - - vi.expect(jitJson( - Number.MAX_SAFE_INTEGER - )).toMatchInlineSnapshot - (`"value === 9007199254740991"`) - - vi.expect(jitJson( - +0 - )).toMatchInlineSnapshot - (`"value === +0"`) - - vi.expect(jitJson( - -0 - )).toMatchInlineSnapshot - (`"value === -0"`) - - vi.expect(jitJson( - 1 / 3 - )).toMatchInlineSnapshot - (`"value === 0.3333333333333333"`) - - vi.expect(jitJson( - -1 / 3 - )).toMatchInlineSnapshot - (`"value === -0.3333333333333333"`) - - vi.expect(jitJson( - 1e+21 - )).toMatchInlineSnapshot - (`"value === 1e+21"`) - - vi.expect(jitJson( - -1e+21 - )).toMatchInlineSnapshot - (`"value === -1e+21"`) - - vi.expect(jitJson( - 1e-21 - )).toMatchInlineSnapshot - (`"value === 1e-21"`) - - vi.expect(jitJson( - -1e-21 - )).toMatchInlineSnapshot - (`"value === -1e-21"`) - }) - - vi.it('〖⛳️〗› ❲jitJson❳: strings', () => { - vi.expect(jitJson( - '' - )).toMatchInlineSnapshot - (`"value === """`) - - vi.expect(jitJson( - '\\' - )).toMatchInlineSnapshot - (`"value === "\\\\""`) - }) - - vi.it('〖⛳️〗› ❲jitJson❳: objects', () => { - vi.expect(jitJson( - {} - )).toMatchInlineSnapshot - (`"!!value && typeof value === "object" && !Array.isArray(value)"`) - - vi.expect(jitJson( - { - m: { o: 'O' }, - l: ['L'] - } - )).toMatchInlineSnapshot - (` - "!!value && typeof value === "object" && !Array.isArray(value) - && Array.isArray(value.l) && value.l.length === 1 - && value.l[0] === "L" - && !!value.m && typeof value.m === "object" && !Array.isArray(value.m) - && value.m.o === "O"" - `) - + ])( + '〖⛳️〗› ❲t.object❳: check succeeds with valid input (index %#)', + (_) => vi.assert.isTrue(check(_)) + ) }) - vi.it('〖⛳️〗› ❲jitJson❳: arrays', () => { - vi.expect(jitJson( - [] - )).toMatchInlineSnapshot - (`"Array.isArray(value) && value.length === 0"`) - - vi.expect(jitJson( - [1, 2, 3] - )).toMatchInlineSnapshot - (` - "Array.isArray(value) && value.length === 3 - && value[0] === 1 - && value[1] === 2 - && value[2] === 3" - `) - - vi.expect(jitJson( - [[11], [22], [33]] - )).toMatchInlineSnapshot - (` - "Array.isArray(value) && value.length === 3 - && Array.isArray(value[0]) && value[0].length === 1 - && value[0][0] === 11 - && Array.isArray(value[1]) && value[1].length === 1 - && value[1][0] === 22 - && Array.isArray(value[2]) && value[2].length === 1 - && value[2][0] === 33" - `) - - vi.expect(jitJson( - [ - { - a: 3, - b: 3, - c: [5, 6] - }, - { z: 2 }, - 1, - ] - )).toMatchInlineSnapshot - (` - "Array.isArray(value) && value.length === 3 - && value[0] === 1 - && !!value[1] && typeof value[1] === "object" && !Array.isArray(value[1]) - && value[1].z === 2 - && !!value[2] && typeof value[2] === "object" && !Array.isArray(value[2]) - && value[2].a === 3 - && value[2].b === 3 - && Array.isArray(value[2].c) && value[2].c.length === 2 - && value[2].c[0] === 5 - && value[2].c[1] === 6" - `) - - vi.expect(jitJson( - [ - { THREE: [{ A: null, B: false }] }, - { FOUR: [{ A: 1, B: false }], C: '' }, - { TWO: [{ A: null, B: undefined }] }, - { ONE: [true] } - ] - )).toMatchInlineSnapshot - (` - "Array.isArray(value) && value.length === 4 - && !!value[0] && typeof value[0] === "object" && !Array.isArray(value[0]) - && Array.isArray(value[0].ONE) && value[0].ONE.length === 1 - && value[0].ONE[0] === true - && !!value[1] && typeof value[1] === "object" && !Array.isArray(value[1]) - && Array.isArray(value[1].TWO) && value[1].TWO.length === 1 - && !!value[1].TWO[0] && typeof value[1].TWO[0] === "object" && !Array.isArray(value[1].TWO[0]) - && value[1].TWO[0].B === undefined - && value[1].TWO[0].A === null - && !!value[2] && typeof value[2] === "object" && !Array.isArray(value[2]) - && Array.isArray(value[2].THREE) && value[2].THREE.length === 1 - && !!value[2].THREE[0] && typeof value[2].THREE[0] === "object" && !Array.isArray(value[2].THREE[0]) - && value[2].THREE[0].A === null - && value[2].THREE[0].B === false - && !!value[3] && typeof value[3] === "object" && !Array.isArray(value[3]) - && value[3].C === "" - && Array.isArray(value[3].FOUR) && value[3].FOUR.length === 1 - && !!value[3].FOUR[0] && typeof value[3].FOUR[0] === "object" && !Array.isArray(value[3].FOUR[0]) - && value[3].FOUR[0].B === false - && value[3].FOUR[0].A === 1" - `) - - let modularArithmetic = (mod: number, operator: '+' | '*') => { - let index = mod, - row = Array.of(), - col = Array.of(), - matrix = Array.of() - while (index-- !== 0) void ( - row.push(index), - col.push(index), - matrix.push(Array.from({ length: mod })) - ) - for (let i = 0; i < row.length; i++) - for (let j = 0; j < col.length; j++) - matrix[i][j] = (operator === '+' ? i + j : i * j) % mod - // - return matrix - } - - let table = modularArithmetic(5, '*') - - vi.expect(table).toMatchInlineSnapshot - (` - [ - [ - 0, - 0, - 0, - 0, - 0, - ], - [ - 0, - 1, - 2, - 3, - 4, - ], - [ - 0, - 2, - 4, - 1, - 3, - ], - [ - 0, - 3, - 1, - 4, - 2, - ], - [ - 0, - 4, - 3, - 2, - 1, - ], - ] - `) - - vi.expect(jitJson(table)).toMatchInlineSnapshot - (` - "Array.isArray(value) && value.length === 5 - && Array.isArray(value[0]) && value[0].length === 5 - && value[0][0] === +0 - && value[0][1] === +0 - && value[0][2] === +0 - && value[0][3] === +0 - && value[0][4] === +0 - && Array.isArray(value[1]) && value[1].length === 5 - && value[1][0] === +0 - && value[1][1] === 1 - && value[1][2] === 2 - && value[1][3] === 3 - && value[1][4] === 4 - && Array.isArray(value[2]) && value[2].length === 5 - && value[2][0] === +0 - && value[2][1] === 2 - && value[2][2] === 4 - && value[2][3] === 1 - && value[2][4] === 3 - && Array.isArray(value[3]) && value[3].length === 5 - && value[3][0] === +0 - && value[3][1] === 3 - && value[3][2] === 1 - && value[3][3] === 4 - && value[3][4] === 2 - && Array.isArray(value[4]) && value[4].length === 5 - && value[4][0] === +0 - && value[4][1] === 4 - && value[4][2] === 3 - && value[4][3] === 2 - && value[4][4] === 1" - `) - - }) }) vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nullary', () => { vi.it('〖⛳️〗› ❲jit❳: t.never', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.never )).toMatchInlineSnapshot (` @@ -577,7 +312,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla }) vi.it('〖⛳️〗› ❲jit❳: t.any', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.any )).toMatchInlineSnapshot (` @@ -588,7 +323,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla }) vi.it('〖⛳️〗› ❲jit❳: t.unknown', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.unknown )).toMatchInlineSnapshot (` @@ -599,7 +334,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla }) vi.it('〖⛳️〗› ❲jit❳: t.void', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.void )).toMatchInlineSnapshot (` @@ -610,7 +345,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla }) vi.it('〖⛳️〗› ❲jit❳: t.null', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.null )).toMatchInlineSnapshot (` @@ -621,7 +356,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla }) vi.it('〖⛳️〗› ❲jit❳: t.undefined', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.undefined )).toMatchInlineSnapshot (` @@ -632,7 +367,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla }) vi.it('〖⛳️〗› ❲jit❳: t.symbol', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.symbol )).toMatchInlineSnapshot (` @@ -643,7 +378,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla }) vi.it('〖⛳️〗› ❲jit❳: t.boolean', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.boolean )).toMatchInlineSnapshot (` @@ -658,7 +393,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: nulla vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: boundable', () => { vi.it('〖⛳️〗› ❲jit❳: t.integer', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.integer )).toMatchInlineSnapshot (` @@ -669,7 +404,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.integer.min(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.integer.min(0) )).toMatchInlineSnapshot (` @@ -680,7 +415,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.integer.max(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.integer.max(1) )).toMatchInlineSnapshot (` @@ -691,7 +426,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.integer.min(x).max(y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.integer .min(0) .max(1) @@ -702,7 +437,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.integer .max(1) .min(0) @@ -715,7 +450,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.integer.between(x, y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.integer.between(0, 1) )).toMatchInlineSnapshot (` @@ -724,7 +459,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.integer.between(1, 0) )).toMatchInlineSnapshot (` @@ -735,7 +470,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.bigint', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.bigint )).toMatchInlineSnapshot (` @@ -746,7 +481,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.bigint.min(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.bigint.min(0n) )).toMatchInlineSnapshot (` @@ -757,7 +492,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.bigint.max(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.bigint.max(1n) )).toMatchInlineSnapshot (` @@ -768,7 +503,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.bigint.min(x).max(y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.bigint .min(0n) .max(1n) @@ -779,7 +514,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.bigint .max(1n) .min(0n) @@ -792,7 +527,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.bigint.between(x, y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.bigint.between(0n, 1n) )).toMatchInlineSnapshot (` @@ -801,7 +536,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.bigint.between(1n, 0n) )).toMatchInlineSnapshot (` @@ -812,7 +547,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number )).toMatchInlineSnapshot (` @@ -823,7 +558,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number.min(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number.min(0) )).toMatchInlineSnapshot (` @@ -834,7 +569,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number.max(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number.max(1) )).toMatchInlineSnapshot (` @@ -845,7 +580,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number.min(x).max(y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number .min(0) .max(1) @@ -856,7 +591,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.number .max(1) .min(0) @@ -869,7 +604,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number.between(x, y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number.between(0, 1) )).toMatchInlineSnapshot (` @@ -878,7 +613,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.number.between(1, 0) )).toMatchInlineSnapshot (` @@ -889,7 +624,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number.moreThan(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number.moreThan(0) )).toMatchInlineSnapshot (` @@ -900,7 +635,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number.lessThan(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number.lessThan(1) )).toMatchInlineSnapshot (` @@ -911,7 +646,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number.moreThan(x).lessThan(y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number .moreThan(0) .lessThan(1) @@ -922,7 +657,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.number .lessThan(1) .moreThan(0) @@ -935,7 +670,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number.min(x).lessThan(y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number .min(0) .lessThan(1) @@ -946,7 +681,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.number .lessThan(1) .min(0) @@ -959,7 +694,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.number.moreThan(x).max(y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.number .moreThan(0) .max(1) @@ -970,7 +705,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.number .max(1) .moreThan(0) @@ -983,7 +718,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.string', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.string )).toMatchInlineSnapshot (` @@ -994,7 +729,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.string.min(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.string.min(0) )).toMatchInlineSnapshot (` @@ -1005,7 +740,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.string.max(x)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.string.max(1) )).toMatchInlineSnapshot (` @@ -1016,7 +751,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.string.min(x).max(y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.string .min(0) .max(1) @@ -1027,7 +762,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.string .max(1) .min(0) @@ -1040,7 +775,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }) vi.it('〖⛳️〗› ❲jit❳: t.string.between(x, y)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.string.between(0, 1) )).toMatchInlineSnapshot (` @@ -1049,7 +784,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.string.between(1, 0) )).toMatchInlineSnapshot (` @@ -1064,7 +799,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: bound vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary', () => { vi.it('〖⛳️〗› ❲jit❳: t.eq(...)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.eq({ l: 'L', m: 'M' @@ -1078,7 +813,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.eq( [ { @@ -1108,7 +843,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.eq( [ 1, @@ -1139,7 +874,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }) vi.it('〖⛳️〗› ❲jit❳: t.optional(...)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.optional(t.eq(1)) )).toMatchInlineSnapshot (` @@ -1148,7 +883,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.optional(t.optional(t.eq(1))) )).toMatchInlineSnapshot (` @@ -1157,7 +892,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.optional( t.union( t.eq(1000), @@ -1180,7 +915,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.optional( t.union( t.eq(1000), @@ -1214,7 +949,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }) vi.it('〖⛳️〗› ❲jit❳: t.array(...)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.array(t.eq(1)) )).toMatchInlineSnapshot (` @@ -1223,7 +958,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.array(t.array(t.eq(2))) )).toMatchInlineSnapshot (` @@ -1232,7 +967,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.array(t.array(t.array(t.array(t.array(t.array(t.eq(3))))))) )).toMatchInlineSnapshot (` @@ -1253,7 +988,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }) vi.it('〖⛳️〗› ❲jit❳: t.record(...)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.record(t.eq(1)) )).toMatchInlineSnapshot (` @@ -1268,7 +1003,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.record(t.record(t.eq(2))) )).toMatchInlineSnapshot (` @@ -1291,7 +1026,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary vi.it('〖⛳️〗› ❲jit❳: t.union(...)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.union() )).toMatchInlineSnapshot (` @@ -1300,7 +1035,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.union(t.never) )).toMatchInlineSnapshot (` @@ -1309,7 +1044,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.union(t.unknown) )).toMatchInlineSnapshot (` @@ -1318,7 +1053,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.union(t.union()) )).toMatchInlineSnapshot (` @@ -1327,7 +1062,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.union( t.integer, t.bigint, @@ -1340,7 +1075,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.union( t.boolean, t.symbol, @@ -1363,7 +1098,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.union( t.object({ a: t.eq(1), @@ -1386,7 +1121,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.union( t.eq(9000), t.union( @@ -1418,7 +1153,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }) vi.it('〖⛳️〗› ❲jit❳: t.intersect(...)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.intersect(t.unknown) )).toMatchInlineSnapshot (` @@ -1427,7 +1162,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.intersect( t.object({ a: t.eq(1), @@ -1450,7 +1185,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.intersect( t.eq(9000), t.intersect( @@ -1482,7 +1217,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }) vi.it('〖⛳️〗› ❲jit❳: t.tuple(...)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.tuple() )).toMatchInlineSnapshot (` @@ -1491,7 +1226,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.tuple(t.unknown) )).toMatchInlineSnapshot (` @@ -1500,7 +1235,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.tuple( t.tuple(), t.tuple(), @@ -1516,7 +1251,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.tuple( t.tuple( t.eq('[0][0]'), @@ -1536,7 +1271,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.tuple( t.tuple( t.eq('[0][0]'), @@ -1558,7 +1293,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.tuple( t.tuple( t.tuple( @@ -1664,7 +1399,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary vi.it('〖⛳️〗› ❲jit❳: object(...)', () => { - vi.expect(jit( + vi.expect(Jit.generate( t.object({}) )).toMatchInlineSnapshot (` @@ -1673,7 +1408,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.object({ A: t.optional(t.number.min(1)), }) @@ -1687,7 +1422,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.object({ A: t.optional(t.number), B: t.array(t.integer) @@ -1703,7 +1438,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.object({ B: t.array(t.integer), A: t.object({ @@ -1722,7 +1457,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.object({ A: t.union( t.object({ @@ -1752,7 +1487,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.object({ a: t.record(t.object({ b: t.string, @@ -1775,7 +1510,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.object({ F: t.union( t.object({ F: t.number }), @@ -1795,7 +1530,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: unary }" `) - vi.expect(jit( + vi.expect(Jit.generate( t.object({ "#1C": t.object({ twoC: t.intersect( @@ -1921,7 +1656,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: confi schema: { treatArraysAsObjects: false, } - }) && vi.expect(jit(schema)).toMatchInlineSnapshot + }) && vi.expect(Jit.generate(schema)).toMatchInlineSnapshot (` "function check(value) { return ( @@ -1938,7 +1673,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: confi schema: { treatArraysAsObjects: true } - }) && vi.expect(jit(schema)).toMatchInlineSnapshot + }) && vi.expect(Jit.generate(schema)).toMatchInlineSnapshot (` "function check(value) { return ( @@ -1967,7 +1702,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: confi schema: { optionalTreatment: 'exactOptional', } - }) && vi.expect(jit(schema)).toMatchInlineSnapshot + }) && vi.expect(Jit.generate(schema)).toMatchInlineSnapshot (` "function check(value) { return ( @@ -1983,7 +1718,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: confi schema: { optionalTreatment: 'presentButUndefinedIsOK', } - }) && vi.expect(jit(schema)).toMatchInlineSnapshot + }) && vi.expect(Jit.generate(schema)).toMatchInlineSnapshot (` "function check(value) { return ( diff --git a/packages/schema-jit-compiler/test/json.test.ts b/packages/schema-jit-compiler/test/json.test.ts index 27e7cbd5..d5a915e7 100644 --- a/packages/schema-jit-compiler/test/json.test.ts +++ b/packages/schema-jit-compiler/test/json.test.ts @@ -1,25 +1,321 @@ import * as vi from 'vitest' -import { fc, test } from '@fast-check/vitest' -import { t } from '@traversable/schema-core' -import type * as T from '@traversable/registry' -import { fn, URI, symbol } from '@traversable/registry' -import { Seed } from '@traversable/schema-seed' -import type { Algebra, Index } from '@traversable/schema-jit-compiler' import { Json } from '@traversable/schema-jit-compiler' -import { getJsonWeight as getWeight, sortJson as sort } from '@traversable/schema-jit-compiler' +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: Jit.Json', () => { + vi.it('〖⛳️〗› ❲Json.generate❳: throws given non-JSON input', () => { + /* @ts-expect-error */ + vi.assert.throws(() => Json.generate(Symbol())) -vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () => { - vi.it('〖⛳️〗› ❲Json.unfoldComparator❳', () => { - vi.expect(getWeight(null)).toMatchInlineSnapshot(`2`) - vi.expect(getWeight(undefined)).toMatchInlineSnapshot(`1`) - vi.expect(getWeight(false)).toMatchInlineSnapshot(`4`) - vi.expect(getWeight(true)).toMatchInlineSnapshot(`4`) - vi.expect(getWeight([true, false, ['heyy']])).toMatchInlineSnapshot(`280`) + /* @ts-expect-error */ + vi.assert.throws(() => Json.generate(1n)) + }) + + vi.it('〖⛳️〗› ❲Json.generate❳: null', () => { + vi.expect(Json.generate( + null + )).toMatchInlineSnapshot + + (`"value === null"`) + }) + + vi.it('〖⛳️〗› ❲Json.generate❳: undefined', () => { + vi.expect(Json.generate( + undefined + )).toMatchInlineSnapshot + (`"value === undefined"`) + }) + + vi.it('〖⛳️〗› ❲Json.generate❳: booleans', () => { + vi.expect(Json.generate( + false + )).toMatchInlineSnapshot + (`"value === false"`) + + vi.expect(Json.generate( + true + )).toMatchInlineSnapshot + (`"value === true"`) + }) + + vi.it('〖⛳️〗› ❲Json.generate❳: numbers', () => { + vi.expect(Json.generate( + Number.MIN_SAFE_INTEGER + )).toMatchInlineSnapshot + (`"value === -9007199254740991"`) + + vi.expect(Json.generate( + Number.MAX_SAFE_INTEGER + )).toMatchInlineSnapshot + (`"value === 9007199254740991"`) + + vi.expect(Json.generate( + +0 + )).toMatchInlineSnapshot + (`"value === +0"`) + + vi.expect(Json.generate( + -0 + )).toMatchInlineSnapshot + (`"value === -0"`) + + vi.expect(Json.generate( + 1 / 3 + )).toMatchInlineSnapshot + (`"value === 0.3333333333333333"`) + + vi.expect(Json.generate( + -1 / 3 + )).toMatchInlineSnapshot + (`"value === -0.3333333333333333"`) + + vi.expect(Json.generate( + 1e+21 + )).toMatchInlineSnapshot + (`"value === 1e+21"`) + + vi.expect(Json.generate( + -1e+21 + )).toMatchInlineSnapshot + (`"value === -1e+21"`) + + vi.expect(Json.generate( + 1e-21 + )).toMatchInlineSnapshot + (`"value === 1e-21"`) + + vi.expect(Json.generate( + -1e-21 + )).toMatchInlineSnapshot + (`"value === -1e-21"`) + }) + + vi.it('〖⛳️〗› ❲Json.generate❳: strings', () => { + vi.expect(Json.generate( + '' + )).toMatchInlineSnapshot + (`"value === """`) + + vi.expect(Json.generate( + '\\' + )).toMatchInlineSnapshot + (`"value === "\\\\""`) + }) + + vi.it('〖⛳️〗› ❲Json.generate❳: objects', () => { + vi.expect(Json.generate( + {} + )).toMatchInlineSnapshot + (`"!!value && typeof value === "object" && !Array.isArray(value)"`) + + vi.expect(Json.generate( + { + m: { o: 'O' }, + l: ['L'] + } + )).toMatchInlineSnapshot + (` + "!!value && typeof value === "object" && !Array.isArray(value) + && Array.isArray(value.l) && value.l.length === 1 + && value.l[0] === "L" + && !!value.m && typeof value.m === "object" && !Array.isArray(value.m) + && value.m.o === "O"" + `) - vi.expect(sort([1, { a: 3, b: 3, c: [5, 6] }, { z: 2 }])).toMatchInlineSnapshot(` + }) + + vi.it('〖⛳️〗› ❲Json.generate❳: arrays', () => { + vi.expect(Json.generate( + [] + )).toMatchInlineSnapshot + (`"Array.isArray(value) && value.length === 0"`) + + vi.expect(Json.generate( + [1, 2, 3] + )).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 3 + && value[0] === 1 + && value[1] === 2 + && value[2] === 3" + `) + + vi.expect(Json.generate( + [[11], [22], [33]] + )).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 3 + && Array.isArray(value[0]) && value[0].length === 1 + && value[0][0] === 11 + && Array.isArray(value[1]) && value[1].length === 1 + && value[1][0] === 22 + && Array.isArray(value[2]) && value[2].length === 1 + && value[2][0] === 33" + `) + + vi.expect(Json.generate( + [ + { + a: 3, + b: 3, + c: [5, 6] + }, + { z: 2 }, + 1, + ] + )).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 3 + && value[0] === 1 + && !!value[1] && typeof value[1] === "object" && !Array.isArray(value[1]) + && value[1].z === 2 + && !!value[2] && typeof value[2] === "object" && !Array.isArray(value[2]) + && value[2].a === 3 + && value[2].b === 3 + && Array.isArray(value[2].c) && value[2].c.length === 2 + && value[2].c[0] === 5 + && value[2].c[1] === 6" + `) + + vi.expect(Json.generate( + [ + { THREE: [{ A: null, B: false }] }, + { FOUR: [{ A: 1, B: false }], C: '' }, + { TWO: [{ A: null, B: undefined }] }, + { ONE: [true] } + ] + )).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 4 + && !!value[0] && typeof value[0] === "object" && !Array.isArray(value[0]) + && Array.isArray(value[0].ONE) && value[0].ONE.length === 1 + && value[0].ONE[0] === true + && !!value[1] && typeof value[1] === "object" && !Array.isArray(value[1]) + && Array.isArray(value[1].TWO) && value[1].TWO.length === 1 + && !!value[1].TWO[0] && typeof value[1].TWO[0] === "object" && !Array.isArray(value[1].TWO[0]) + && value[1].TWO[0].B === undefined + && value[1].TWO[0].A === null + && !!value[2] && typeof value[2] === "object" && !Array.isArray(value[2]) + && Array.isArray(value[2].THREE) && value[2].THREE.length === 1 + && !!value[2].THREE[0] && typeof value[2].THREE[0] === "object" && !Array.isArray(value[2].THREE[0]) + && value[2].THREE[0].A === null + && value[2].THREE[0].B === false + && !!value[3] && typeof value[3] === "object" && !Array.isArray(value[3]) + && value[3].C === "" + && Array.isArray(value[3].FOUR) && value[3].FOUR.length === 1 + && !!value[3].FOUR[0] && typeof value[3].FOUR[0] === "object" && !Array.isArray(value[3].FOUR[0]) + && value[3].FOUR[0].B === false + && value[3].FOUR[0].A === 1" + `) + + let modularArithmetic = (mod: number, operator: '+' | '*') => { + let index = mod, + row = Array.of(), + col = Array.of(), + matrix = Array.of() + while (index-- !== 0) void ( + row.push(index), + col.push(index), + matrix.push(Array.from({ length: mod })) + ) + for (let i = 0; i < row.length; i++) + for (let j = 0; j < col.length; j++) + matrix[i][j] = (operator === '+' ? i + j : i * j) % mod + // + return matrix + } + + let table = modularArithmetic(5, '*') + + vi.expect(table).toMatchInlineSnapshot + (` + [ + [ + 0, + 0, + 0, + 0, + 0, + ], + [ + 0, + 1, + 2, + 3, + 4, + ], + [ + 0, + 2, + 4, + 1, + 3, + ], + [ + 0, + 3, + 1, + 4, + 2, + ], + [ + 0, + 4, + 3, + 2, + 1, + ], + ] + `) + + vi.expect(Json.generate(table)).toMatchInlineSnapshot + (` + "Array.isArray(value) && value.length === 5 + && Array.isArray(value[0]) && value[0].length === 5 + && value[0][0] === +0 + && value[0][1] === +0 + && value[0][2] === +0 + && value[0][3] === +0 + && value[0][4] === +0 + && Array.isArray(value[1]) && value[1].length === 5 + && value[1][0] === +0 + && value[1][1] === 1 + && value[1][2] === 2 + && value[1][3] === 3 + && value[1][4] === 4 + && Array.isArray(value[2]) && value[2].length === 5 + && value[2][0] === +0 + && value[2][1] === 2 + && value[2][2] === 4 + && value[2][3] === 1 + && value[2][4] === 3 + && Array.isArray(value[3]) && value[3].length === 5 + && value[3][0] === +0 + && value[3][1] === 3 + && value[3][2] === 1 + && value[3][3] === 4 + && value[3][4] === 2 + && Array.isArray(value[4]) && value[4].length === 5 + && value[4][0] === +0 + && value[4][1] === 4 + && value[4][2] === 3 + && value[4][3] === 2 + && value[4][4] === 1" + `) + + }) + + vi.it('〖⛳️〗› ❲Json.getWeight❳', () => { + vi.expect(Json.getWeight(null)).toMatchInlineSnapshot(`2`) + vi.expect(Json.getWeight(undefined)).toMatchInlineSnapshot(`1`) + vi.expect(Json.getWeight(false)).toMatchInlineSnapshot(`4`) + vi.expect(Json.getWeight(true)).toMatchInlineSnapshot(`4`) + vi.expect(Json.getWeight([true, false, ['heyy']])).toMatchInlineSnapshot(`280`) + }) + + vi.it('〖⛳️〗› ❲Json.sort❳', () => { + vi.expect(Json.sort([1, { a: 3, b: 3, c: [5, 6] }, { z: 2 }])).toMatchInlineSnapshot(` { "def": [ { @@ -78,7 +374,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () = } `) - vi.expect(sort([{ a: 2 }, { a: true }])).toMatchInlineSnapshot(` + vi.expect(Json.sort([{ a: 2 }, { a: true }])).toMatchInlineSnapshot(` { "def": [ { @@ -110,7 +406,7 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () = } `) - vi.expect(sort([{ a: [[10]] }, { a: [[false]] }])).toMatchInlineSnapshot(` + vi.expect(Json.sort([{ a: [[10]] }, { a: [[false]] }])).toMatchInlineSnapshot(` { "def": [ { @@ -161,7 +457,5 @@ vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () = "tag": "@traversable/schema-core/URI::array", } `) - - }) }) diff --git a/packages/schema-jit-compiler/test/sort.test.ts b/packages/schema-jit-compiler/test/sort.test.ts index 7244abeb..a500156c 100644 --- a/packages/schema-jit-compiler/test/sort.test.ts +++ b/packages/schema-jit-compiler/test/sort.test.ts @@ -1,34 +1,34 @@ import * as vi from 'vitest' import { t } from '@traversable/schema-core' -import { jit, WeightByTypeName } from '@traversable/schema-jit-compiler' - -let SHALLOW_ORDER = { - [WeightByTypeName.never]: t.never, - [WeightByTypeName.any]: t.any, - [WeightByTypeName.unknown]: t.unknown, - [WeightByTypeName.void]: t.void, - [WeightByTypeName.null]: t.null, - [WeightByTypeName.undefined]: t.undefined, - [WeightByTypeName.symbol]: t.symbol, - [WeightByTypeName.boolean]: t.boolean, - [WeightByTypeName.integer]: t.integer, - [WeightByTypeName.bigint]: t.bigint, - [WeightByTypeName.number]: t.number, - [WeightByTypeName.string]: t.string, - [WeightByTypeName.eq]: t.eq({}), - [WeightByTypeName.optional]: t.optional(t.never), - [WeightByTypeName.array]: t.array(t.never), - [WeightByTypeName.record]: t.record(t.never), - [WeightByTypeName.intersect]: t.intersect(), - [WeightByTypeName.union]: t.union(), - [WeightByTypeName.tuple]: t.tuple(), - [WeightByTypeName.object]: t.object({}), -} +import { Jit } from '@traversable/schema-jit-compiler' vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳', () => { vi.it('〖⛳️〗› ❲sort❳: shallow sort order is correct', () => { - vi.expect(jit(t.object(SHALLOW_ORDER))).toMatchInlineSnapshot(` + vi.expect(Jit.generate( + t.object({ + [Jit.WeightByTypeName.never]: t.never, + [Jit.WeightByTypeName.any]: t.any, + [Jit.WeightByTypeName.unknown]: t.unknown, + [Jit.WeightByTypeName.void]: t.void, + [Jit.WeightByTypeName.null]: t.null, + [Jit.WeightByTypeName.undefined]: t.undefined, + [Jit.WeightByTypeName.symbol]: t.symbol, + [Jit.WeightByTypeName.boolean]: t.boolean, + [Jit.WeightByTypeName.integer]: t.integer, + [Jit.WeightByTypeName.bigint]: t.bigint, + [Jit.WeightByTypeName.number]: t.number, + [Jit.WeightByTypeName.string]: t.string, + [Jit.WeightByTypeName.eq]: t.eq({}), + [Jit.WeightByTypeName.optional]: t.optional(t.never), + [Jit.WeightByTypeName.array]: t.array(t.never), + [Jit.WeightByTypeName.record]: t.record(t.never), + [Jit.WeightByTypeName.intersect]: t.intersect(), + [Jit.WeightByTypeName.union]: t.union(), + [Jit.WeightByTypeName.tuple]: t.tuple(), + [Jit.WeightByTypeName.object]: t.object({}), + }) + )).toMatchInlineSnapshot(` "function check(value) { return ( !!value && typeof value === "object" && !Array.isArray(value) From a8bc572cd66444e857a0c14330559e9c99f60721 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 17 Apr 2025 03:38:20 -0500 Subject: [PATCH 40/45] fix(jit): fixes broken import, auto-gen changes --- .../schema-jit-compiler/src/__generated__/__manifest__.ts | 3 ++- packages/schema-jit-compiler/src/exports.ts | 4 ++-- packages/schema-jit-compiler/src/json.ts | 4 ++-- packages/schema-jit-compiler/src/version.ts | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/schema-jit-compiler/src/__generated__/__manifest__.ts b/packages/schema-jit-compiler/src/__generated__/__manifest__.ts index 64ff05e4..af3019f2 100644 --- a/packages/schema-jit-compiler/src/__generated__/__manifest__.ts +++ b/packages/schema-jit-compiler/src/__generated__/__manifest__.ts @@ -41,6 +41,7 @@ export default { }, "devDependencies": { "@traversable/registry": "workspace:^", - "@traversable/schema-core": "workspace:^" + "@traversable/schema-core": "workspace:^", + "@traversable/schema-seed": "workspace:^" } } as const \ No newline at end of file diff --git a/packages/schema-jit-compiler/src/exports.ts b/packages/schema-jit-compiler/src/exports.ts index 1c8cbf77..bebefc24 100644 --- a/packages/schema-jit-compiler/src/exports.ts +++ b/packages/schema-jit-compiler/src/exports.ts @@ -5,9 +5,9 @@ export { WeightByTypeName, } from './jit.js' export * as Json from './json.js' -export type { Index } from './shared.js' +export type { Index } from './functor.js' +export { defaultIndex } from './functor.js' export { - defaultIndex, indexAccessor, keyAccessor, } from './shared.js' diff --git a/packages/schema-jit-compiler/src/json.ts b/packages/schema-jit-compiler/src/json.ts index fdd48b25..ad71764d 100644 --- a/packages/schema-jit-compiler/src/json.ts +++ b/packages/schema-jit-compiler/src/json.ts @@ -2,7 +2,7 @@ import type * as T from '@traversable/registry' import { escape, fn, isValidIdentifier, Object_entries, Object_values, URI } from '@traversable/registry' import { Json } from '@traversable/json' -import type { Index as Ix } from './shared.js' +import type * as F from './functor.js' import { buildContext } from './shared.js' export let isScalar = Json.isScalar @@ -31,7 +31,7 @@ export type Fixpoint = | { tag: URI.array, def: Fixpoint[] } | { tag: URI.object, def: [k: string, v: Fixpoint][] } -export type Index = Omit +export type Index = Omit export type Algebra = T.IndexedAlgebra export let defaultIndex = { diff --git a/packages/schema-jit-compiler/src/version.ts b/packages/schema-jit-compiler/src/version.ts index 388bbc3e..660ff1ca 100644 --- a/packages/schema-jit-compiler/src/version.ts +++ b/packages/schema-jit-compiler/src/version.ts @@ -1,3 +1,3 @@ import pkg from './__generated__/__manifest__.js' export const VERSION = `${pkg.name}@${pkg.version}` as const -export type VERSION = typeof VERSION \ No newline at end of file +export type VERSION = typeof VERSION From 256392353f02fb2eeb24a4ed5cfbac107b31b859 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 17 Apr 2025 03:59:13 -0500 Subject: [PATCH 41/45] init(arbitrary): initializes new package `@traversable/schema-arbitrary` --- README.md | 2 + config/__generated__/package-list.ts | 1 + packages/derive-validators/src/errors.ts | 43 +++++++++++-- packages/derive-validators/src/exports.ts | 29 +++++---- packages/schema-arbitrary/README.md | 60 +++++++++++++++++++ packages/schema-arbitrary/package.json | 52 ++++++++++++++++ .../src/__generated__/__manifest__.ts | 52 ++++++++++++++++ .../src/arbitrary.ts} | 4 +- packages/schema-arbitrary/src/exports.ts | 2 + packages/schema-arbitrary/src/index.ts | 1 + packages/schema-arbitrary/src/version.ts | 3 + .../schema-arbitrary/test/version.test.ts | 10 ++++ packages/schema-arbitrary/tsconfig.build.json | 11 ++++ packages/schema-arbitrary/tsconfig.json | 8 +++ packages/schema-arbitrary/tsconfig.src.json | 11 ++++ packages/schema-arbitrary/tsconfig.test.json | 15 +++++ packages/schema-arbitrary/vite.config.ts | 6 ++ packages/schema-jit-compiler/package.json | 5 ++ .../src/__generated__/__manifest__.ts | 5 ++ packages/schema-jit-compiler/src/exports.ts | 2 +- packages/schema-jit-compiler/test/jit.test.ts | 3 +- .../schema-jit-compiler/tsconfig.test.json | 1 + pnpm-lock.yaml | 17 ++++++ tsconfig.base.json | 4 ++ tsconfig.build.json | 1 + tsconfig.json | 1 + 26 files changed, 324 insertions(+), 25 deletions(-) create mode 100644 packages/schema-arbitrary/README.md create mode 100644 packages/schema-arbitrary/package.json create mode 100644 packages/schema-arbitrary/src/__generated__/__manifest__.ts rename packages/{schema-jit-compiler/test/TODO.ts => schema-arbitrary/src/arbitrary.ts} (97%) create mode 100644 packages/schema-arbitrary/src/exports.ts create mode 100644 packages/schema-arbitrary/src/index.ts create mode 100644 packages/schema-arbitrary/src/version.ts create mode 100644 packages/schema-arbitrary/test/version.test.ts create mode 100644 packages/schema-arbitrary/tsconfig.build.json create mode 100644 packages/schema-arbitrary/tsconfig.json create mode 100644 packages/schema-arbitrary/tsconfig.src.json create mode 100644 packages/schema-arbitrary/tsconfig.test.json create mode 100644 packages/schema-arbitrary/vite.config.ts diff --git a/README.md b/README.md index 3b63eb15..9296fabc 100644 --- a/README.md +++ b/README.md @@ -392,6 +392,8 @@ flowchart TD derive-validators(derive-validators) -.-> json(json) derive-validators(derive-validators) -.-> registry(registry) derive-validators(derive-validators) -.-> schema-core(schema-core) + schema-arbitrary(schema-arbitrary) -.-> registry(registry) + schema-arbitrary(schema-arbitrary) -.-> schema-core(schema-core) schema-jit-compiler(schema-jit-compiler) -.-> registry(registry) schema-jit-compiler(schema-jit-compiler) -.-> schema-core(schema-core) schema-seed(schema-seed) -.-> json(json) diff --git a/config/__generated__/package-list.ts b/config/__generated__/package-list.ts index 8862b0df..30420693 100644 --- a/config/__generated__/package-list.ts +++ b/config/__generated__/package-list.ts @@ -5,6 +5,7 @@ export const PACKAGES = [ "packages/json", "packages/registry", "packages/schema", + "packages/schema-arbitrary", "packages/schema-core", "packages/schema-generator", "packages/schema-jit-compiler", diff --git a/packages/derive-validators/src/errors.ts b/packages/derive-validators/src/errors.ts index 23255d41..4330cccd 100644 --- a/packages/derive-validators/src/errors.ts +++ b/packages/derive-validators/src/errors.ts @@ -38,7 +38,14 @@ export const ErrorType = { OutOfBounds: 'OUT_OF_BOUNDS', } as const satisfies Record -function error(kind: T, path: (keyof any)[], got: unknown, msg: string | undefined, expected: unknown, schemaPath: (keyof any)[]): { +function error( + kind: T, + path: (keyof any)[], + got: unknown, + msg: string | undefined, + expected: unknown, + schemaPath: (keyof any)[] +): { kind: typeof kind path: typeof path got: typeof got @@ -46,25 +53,51 @@ function error(kind: T, path: (keyof any)[], got: unknown, msg expected: typeof expected schemaPath: typeof schemaPath } -function error(kind: T, path: (keyof any)[], got: unknown, msg: string | undefined, expected: unknown): { + +function error( + kind: T, + path: (keyof any)[], + got: unknown, + msg: string | undefined, + expected: unknown +): { kind: typeof kind path: typeof path got: typeof got msg: typeof msg expected: typeof expected } -function error(kind: T, path: (keyof any)[], got: unknown, msg: string): { + +function error( + kind: T, + path: (keyof any)[], + got: unknown, + msg: string +): { kind: typeof kind path: typeof path got: typeof got msg: typeof msg } -function error(kind: T, path: (keyof any)[], got: unknown): { + +function error( + kind: T, + path: (keyof any)[], + got: unknown +): { kind: typeof kind path: typeof path got: typeof got } -function error(kind: T, path: (keyof any)[], got: unknown, msg?: string, expected?: unknown, schemaPath?: (keyof any)[]): ValidationError { + +function error( + kind: T, + path: (keyof any)[], + got: unknown, + msg?: string, + expected?: unknown, + schemaPath?: (keyof any)[] +): ValidationError { return { kind, path: dataPath(path), diff --git a/packages/derive-validators/src/exports.ts b/packages/derive-validators/src/exports.ts index b0aac5ad..96fb3b2b 100644 --- a/packages/derive-validators/src/exports.ts +++ b/packages/derive-validators/src/exports.ts @@ -1,22 +1,7 @@ -export { fromSchema, fromSchemaWithOptions } from './recursive.js' - export { VERSION } from './version.js' - -export type { - ValidationFn, - Validate, - Options, -} from './shared.js' -export { - hasOptionalSymbol, - hasValidate, - callValidate, -} from './shared.js' - export type { ValidationError, } from './errors.js' - export { NULLARY as NullaryErrors, UNARY as UnaryErrors, @@ -24,3 +9,17 @@ export { ErrorType, dataPath as dataPathFromSchemaPath, } from './errors.js' +export { + fromSchema, + fromSchemaWithOptions, +} from './recursive.js' +export type { + ValidationFn, + Validate, + Options, +} from './shared.js' +export { + hasOptionalSymbol, + hasValidate, + callValidate, +} from './shared.js' diff --git a/packages/schema-arbitrary/README.md b/packages/schema-arbitrary/README.md new file mode 100644 index 00000000..173c4050 --- /dev/null +++ b/packages/schema-arbitrary/README.md @@ -0,0 +1,60 @@ +
+

ᯓ𝘁𝗿𝗮𝘃𝗲𝗿𝘀𝗮𝗯𝗹𝗲/𝘀𝗰𝗵𝗲𝗺𝗮-𝗮𝗿𝗯𝗶𝘁𝗿𝗮𝗿𝘆

+
+ +

+ Derive a [fast-check](https://github.com/dubzzz/fast-check) arbitrary from a schema from `@traversable/schema-core`. +

+ +
+ NPM Version +   + TypeScript +   + Static Badge +   + npm +   +
+ +
+ npm bundle size (scoped) +   + Static Badge +   + Static Badge +   +
+ +
+ Demo (StackBlitz) +   •   + TypeScript Playground +   •   + npm +
+
+
+
+ +## Installation + +`@traversable/schema-arbitrary` has a peer depenency on [fast-check](https://github.com/dubzzz/fast-check). It has +been tested with version 3 and version 4. + +## Getting Started + +```typescript +import { t } from '@traversable/schema' +import { Arbitrary } from '@traversable/schema-arbitrary' +import * as fc from 'fast-check' + +let schema = t.object({ + a: t.optional(t.string), + b: t.integer.between(0, 100), +}) + +let arbitrary = Arbitrary.fromSchema(schema) + +let giveMe100Mocks = fc.sample(arbitrary, 100) +``` diff --git a/packages/schema-arbitrary/package.json b/packages/schema-arbitrary/package.json new file mode 100644 index 00000000..d1c0079b --- /dev/null +++ b/packages/schema-arbitrary/package.json @@ -0,0 +1,52 @@ +{ + "name": "@traversable/schema-arbitrary", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/schema-arbitrary" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "fast-check": "3 - 4", + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^" + }, + "peerDependenciesMeta": { + "fast-check": { "optional": false }, + "@traversable/registry": { "optional": false }, + "@traversable/schema-core": { "optional": false } + }, + "devDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^" + } +} diff --git a/packages/schema-arbitrary/src/__generated__/__manifest__.ts b/packages/schema-arbitrary/src/__generated__/__manifest__.ts new file mode 100644 index 00000000..9d7b0d48 --- /dev/null +++ b/packages/schema-arbitrary/src/__generated__/__manifest__.ts @@ -0,0 +1,52 @@ +export default { + "name": "@traversable/schema-arbitrary", + "type": "module", + "version": "0.0.0", + "private": false, + "description": "", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/traversable/schema.git", + "directory": "packages/schema-arbitrary" + }, + "bugs": { + "url": "https://github.com/traversable/schema/issues", + "email": "ahrjarrett@gmail.com" + }, + "@traversable": { + "generateExports": { "include": ["**/*.ts"] }, + "generateIndex": { "include": ["**/*.ts"] } + }, + "publishConfig": { + "access": "public", + "directory": "dist", + "registry": "https://registry.npmjs.org" + }, + "scripts": { + "bench": "echo NOTHING TO BENCH", + "build": "pnpm build:esm && pnpm build:cjs && pnpm build:annotate", + "build:annotate": "babel build --plugins annotate-pure-calls --out-dir build --source-maps", + "build:esm": "tsc -b tsconfig.build.json", + "build:cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", + "check": "tsc -b tsconfig.json", + "clean": "pnpm run \"/^clean:.*/\"", + "clean:build": "rm -rf .tsbuildinfo dist build", + "clean:deps": "rm -rf node_modules", + "test": "vitest" + }, + "peerDependencies": { + "fast-check": "3 - 4", + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^" + }, + "peerDependenciesMeta": { + "fast-check": { "optional": false }, + "@traversable/registry": { "optional": false }, + "@traversable/schema-core": { "optional": false } + }, + "devDependencies": { + "@traversable/registry": "workspace:^", + "@traversable/schema-core": "workspace:^" + } +} as const \ No newline at end of file diff --git a/packages/schema-jit-compiler/test/TODO.ts b/packages/schema-arbitrary/src/arbitrary.ts similarity index 97% rename from packages/schema-jit-compiler/test/TODO.ts rename to packages/schema-arbitrary/src/arbitrary.ts index bc2ca4b3..c3a7b5f8 100644 --- a/packages/schema-jit-compiler/test/TODO.ts +++ b/packages/schema-arbitrary/src/arbitrary.ts @@ -10,7 +10,7 @@ declare namespace fromSchema { type Options = {} } */ export let fromSchema : (schema: S, options?: fromSchema.Options) => fc.Arbitrary - = (schema) => t.fold>( + = t.fold>( (x) => { switch (true) { default: return fn.exhaustive(x) @@ -47,4 +47,4 @@ export let fromSchema } } } - )(schema) + ) diff --git a/packages/schema-arbitrary/src/exports.ts b/packages/schema-arbitrary/src/exports.ts new file mode 100644 index 00000000..faef772f --- /dev/null +++ b/packages/schema-arbitrary/src/exports.ts @@ -0,0 +1,2 @@ +export * from './version.js' +export * as Arbitrary from './arbitrary.js' diff --git a/packages/schema-arbitrary/src/index.ts b/packages/schema-arbitrary/src/index.ts new file mode 100644 index 00000000..9fd152fb --- /dev/null +++ b/packages/schema-arbitrary/src/index.ts @@ -0,0 +1 @@ +export * from './exports.js' \ No newline at end of file diff --git a/packages/schema-arbitrary/src/version.ts b/packages/schema-arbitrary/src/version.ts new file mode 100644 index 00000000..660ff1ca --- /dev/null +++ b/packages/schema-arbitrary/src/version.ts @@ -0,0 +1,3 @@ +import pkg from './__generated__/__manifest__.js' +export const VERSION = `${pkg.name}@${pkg.version}` as const +export type VERSION = typeof VERSION diff --git a/packages/schema-arbitrary/test/version.test.ts b/packages/schema-arbitrary/test/version.test.ts new file mode 100644 index 00000000..5ec1baa3 --- /dev/null +++ b/packages/schema-arbitrary/test/version.test.ts @@ -0,0 +1,10 @@ +import * as vi from 'vitest' +import pkg from '../package.json' with { type: 'json' } +import { VERSION } from '@traversable/schema-arbitrary' + +vi.describe('〖⛳️〗‹‹‹ ❲@traversable/schema-arbitrary❳', () => { + vi.it('〖⛳️〗› ❲VERSION❳', () => { + const expected = `${pkg.name}@${pkg.version}` + vi.assert.equal(VERSION, expected) + }) +}) diff --git a/packages/schema-arbitrary/tsconfig.build.json b/packages/schema-arbitrary/tsconfig.build.json new file mode 100644 index 00000000..be49007b --- /dev/null +++ b/packages/schema-arbitrary/tsconfig.build.json @@ -0,0 +1,11 @@ +{ + "extends": "./tsconfig.src.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", + "types": ["node"], + "declarationDir": "build/dts", + "outDir": "build/esm", + "stripInternal": true + }, + "references": [{ "path": "../registry" }, { "path": "../schema-core" }] +} diff --git a/packages/schema-arbitrary/tsconfig.json b/packages/schema-arbitrary/tsconfig.json new file mode 100644 index 00000000..2c291d21 --- /dev/null +++ b/packages/schema-arbitrary/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "include": [], + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "tsconfig.test.json" } + ] +} diff --git a/packages/schema-arbitrary/tsconfig.src.json b/packages/schema-arbitrary/tsconfig.src.json new file mode 100644 index 00000000..702668d2 --- /dev/null +++ b/packages/schema-arbitrary/tsconfig.src.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", + "rootDir": "src", + "types": ["node"], + "outDir": "build/src" + }, + "references": [{ "path": "../registry" }, { "path": "../schema-core" }], + "include": ["src"] +} diff --git a/packages/schema-arbitrary/tsconfig.test.json b/packages/schema-arbitrary/tsconfig.test.json new file mode 100644 index 00000000..58314227 --- /dev/null +++ b/packages/schema-arbitrary/tsconfig.test.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", + "rootDir": "test", + "types": ["node"], + "noEmit": true + }, + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "../registry" }, + { "path": "../schema-core" } + ], + "include": ["test"] +} diff --git a/packages/schema-arbitrary/vite.config.ts b/packages/schema-arbitrary/vite.config.ts new file mode 100644 index 00000000..64dba4ad --- /dev/null +++ b/packages/schema-arbitrary/vite.config.ts @@ -0,0 +1,6 @@ +import { defineConfig, mergeConfig } from 'vitest/config' +import sharedConfig from '../../vite.config.js' + +const localConfig = defineConfig({}) + +export default mergeConfig(sharedConfig, localConfig) \ No newline at end of file diff --git a/packages/schema-jit-compiler/package.json b/packages/schema-jit-compiler/package.json index af2bfe3a..0fd2ab8d 100644 --- a/packages/schema-jit-compiler/package.json +++ b/packages/schema-jit-compiler/package.json @@ -39,8 +39,13 @@ "@traversable/registry": "workspace:^", "@traversable/schema-core": "workspace:^" }, + "peerDependenciesMeta": { + "@traversable/registry": { "optional": false }, + "@traversable/schema-core": { "optional": false } + }, "devDependencies": { "@traversable/registry": "workspace:^", + "@traversable/schema-arbitrary": "workspace:^", "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^" } diff --git a/packages/schema-jit-compiler/src/__generated__/__manifest__.ts b/packages/schema-jit-compiler/src/__generated__/__manifest__.ts index af3019f2..8cc538c4 100644 --- a/packages/schema-jit-compiler/src/__generated__/__manifest__.ts +++ b/packages/schema-jit-compiler/src/__generated__/__manifest__.ts @@ -39,8 +39,13 @@ export default { "@traversable/registry": "workspace:^", "@traversable/schema-core": "workspace:^" }, + "peerDependenciesMeta": { + "@traversable/registry": { "optional": false }, + "@traversable/schema-core": { "optional": false } + }, "devDependencies": { "@traversable/registry": "workspace:^", + "@traversable/schema-arbitrary": "workspace:^", "@traversable/schema-core": "workspace:^", "@traversable/schema-seed": "workspace:^" } diff --git a/packages/schema-jit-compiler/src/exports.ts b/packages/schema-jit-compiler/src/exports.ts index bebefc24..aefa61fa 100644 --- a/packages/schema-jit-compiler/src/exports.ts +++ b/packages/schema-jit-compiler/src/exports.ts @@ -1,4 +1,4 @@ -export * from './version.js' +export { VERSION } from './version.js' export { generate, compile, diff --git a/packages/schema-jit-compiler/test/jit.test.ts b/packages/schema-jit-compiler/test/jit.test.ts index 409ba99c..ac5913c1 100644 --- a/packages/schema-jit-compiler/test/jit.test.ts +++ b/packages/schema-jit-compiler/test/jit.test.ts @@ -4,8 +4,7 @@ import { Seed } from '@traversable/schema-seed' import { t, configure } from '@traversable/schema-core' import { Jit } from '@traversable/schema-jit-compiler' - -import * as Arbitrary from './TODO.js' +import { Arbitrary } from '@traversable/schema-arbitrary' vi.describe.skip('〖⛳️〗‹‹‹ ❲@traversable/schema-jit-compiler❳: Jit.compile w/ randomly generated data', () => { diff --git a/packages/schema-jit-compiler/tsconfig.test.json b/packages/schema-jit-compiler/tsconfig.test.json index 3ad0d988..df338a8c 100644 --- a/packages/schema-jit-compiler/tsconfig.test.json +++ b/packages/schema-jit-compiler/tsconfig.test.json @@ -9,6 +9,7 @@ "references": [ { "path": "tsconfig.src.json" }, { "path": "../registry" }, + { "path": "../schema-arbitrary" }, { "path": "../schema-core" }, { "path": "../schema-seed" } ], diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 850d855a..a5f730af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -263,6 +263,20 @@ importers: version: link:../schema-to-string/dist publishDirectory: dist + packages/schema-arbitrary: + dependencies: + fast-check: + specifier: 3 - 4 + version: 3.23.2 + devDependencies: + '@traversable/registry': + specifier: workspace:^ + version: link:../registry/dist + '@traversable/schema-core': + specifier: workspace:^ + version: link:../schema-core/dist + publishDirectory: dist + packages/schema-core: dependencies: '@traversable/registry': @@ -319,6 +333,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../registry/dist + '@traversable/schema-arbitrary': + specifier: workspace:^ + version: link:../schema-arbitrary/dist '@traversable/schema-core': specifier: workspace:^ version: link:../schema-core/dist diff --git a/tsconfig.base.json b/tsconfig.base.json index 0569aabf..b5034be0 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -43,6 +43,10 @@ "@traversable/registry": ["packages/registry/src/index.js"], "@traversable/registry/*": ["packages/registry/src/*.js"], "@traversable/schema": ["packages/schema/src/index.js"], + "@traversable/schema-arbitrary": [ + "packages/schema-arbitrary/src/index.js" + ], + "@traversable/schema-arbitrary/*": ["packages/schema-arbitrary/*.js"], "@traversable/schema-core": ["packages/schema-core/src/index.js"], "@traversable/schema-core/*": ["packages/schema-core/src/*.js"], "@traversable/schema-generator": [ diff --git a/tsconfig.build.json b/tsconfig.build.json index 2793087a..e60adcaf 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -7,6 +7,7 @@ { "path": "packages/derive-validators/tsconfig.build.json" }, { "path": "packages/json/tsconfig.build.json" }, { "path": "packages/registry/tsconfig.build.json" }, + { "path": "packages/schema-arbitrary/tsconfig.build.json" }, { "path": "packages/schema-core/tsconfig.build.json" }, { "path": "packages/schema-generator/tsconfig.build.json" }, { "path": "packages/schema-jit-compiler/tsconfig.build.json" }, diff --git a/tsconfig.json b/tsconfig.json index 32a98ca4..c6fdd90a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,6 +8,7 @@ { "path": "packages/json" }, { "path": "packages/registry" }, { "path": "packages/schema" }, + { "path": "packages/schema-arbitrary" }, { "path": "packages/schema-core" }, { "path": "packages/schema-generator" }, { "path": "packages/schema-jit-compiler" }, From 15869ad4a87f6954eb329af68b777132f4008ad8 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Mon, 21 Apr 2025 11:40:48 -0500 Subject: [PATCH 42/45] feat(build): adds `bench:types` script --- bin/bench.ts | 214 +++++++++++++++++++++++ package.json | 4 +- packages/schema-core/tsconfig.bench.json | 22 +++ packages/schema-core/tsconfig.json | 10 +- 4 files changed, 246 insertions(+), 4 deletions(-) create mode 100755 bin/bench.ts create mode 100644 packages/schema-core/tsconfig.bench.json diff --git a/bin/bench.ts b/bin/bench.ts new file mode 100755 index 00000000..b98459ef --- /dev/null +++ b/bin/bench.ts @@ -0,0 +1,214 @@ +#!/usr/bin/env pnpm dlx tsx +import * as fs from 'node:fs' +import * as path from 'node:path' +import { execSync } from 'node:child_process' + +import { PACKAGES as packagePaths } from 'bin/constants.js' + +let TAB = ' '.repeat(4) +let NEWLINE = '\r\n' +let CR = '\r' +let INIT_CWD = process.env.INIT_CWD ?? path.resolve() +let PACKAGES = packagePaths.map((path) => path.startsWith('packages/') ? path.slice('packages/'.length) : path) + +let PATHSPEC = { + BENCH_DIR: 'bench', + TSCONFIG: 'tsconfig.json', + TSCONFIG_BENCH: 'tsconfig.bench.json', + TSCONFIG_TMP: 'tsconfig.tmp.json', + TYPELEVEL_BENCHMARK_SUFFIX: '.bench.types.ts', +} as const + +let [, , arg /* , ...worspaces */] = process.argv +let exec = (cmd: string) => execSync(cmd, { stdio: 'inherit' }) + +let Cmd = { + Terms: () => exec('BENCH=true pnpm vitest bench --outputJson benchmarks/benchmark--$(date -Iseconds).json'), + Types: (pkg: string, path: string) => exec(`cd packages/${pkg} && pnpm dlx tsx ${path} --tsVersions '*'`), +} + +let Script = { + run: runBenchmarks, + prepareTypes: () => prepareTypelevelBenchmarks(PACKAGES), + runTypes: () => runTypelevelBenchmarks(PACKAGES), + cleanupTypes: () => cleanupTypelevelBenchmarks(PACKAGES), +} + +let LOG = { + onPrepare: (pkgName: string, PATH: Paths) => { + console.group(`${NEWLINE}Preparing benchmark run for workspace: ${pkgName}${NEWLINE}`) + console.info(`${CR}${TAB}Temporarily moving... ${NEWLINE}${TAB} -> ${PATH.tsconfig}${NEWLINE}${TAB} -> ${PATH.tsconfigTmp}${NEWLINE}`) + console.info(`${CR}${TAB}Temporarily moving... ${NEWLINE}${TAB} -> ${PATH.tsconfigBench}${NEWLINE}${TAB} -> ${PATH.tsconfig}${NEWLINE}`) + console.groupEnd() + }, + onRun: (filePath: string) => { + console.info(`Running typelevel benchmark: ` + filePath) + }, + onCleanup: (pkgName: string, PATH: Paths) => { + console.group(`${NEWLINE}Cleaning up benchmark run for workspace: ${pkgName}${NEWLINE}`) + console.info(`${CR}${TAB}Putting back... ${NEWLINE}${TAB} -> ${PATH.tsconfig}${NEWLINE}${TAB} -> ${PATH.tsconfigBench}${NEWLINE}`) + console.info(`${CR}${TAB}Putting back... ${NEWLINE}${TAB} -> ${PATH.tsconfigTmp}${NEWLINE}${TAB} -> ${PATH.tsconfig}${NEWLINE}`) + console.groupEnd() + }, +} + +let isKeyOf = (x: T, k: keyof any): k is keyof T => !!x && typeof x === 'object' && k in x +let has + : (k: K, guard?: (u: unknown) => u is V) => (x: unknown) => x is Record + = (k, guard) => (x): x is never => !!x && typeof x === 'object' + && globalThis.Object.prototype.hasOwnProperty.call(x, k) + && (guard ? guard(x[k as never]) : true) + +interface Paths { + benchDir: string + tsconfig: string + tsconfigBench: string + tsconfigTmp: string +} + +let makePaths + : (pkgName: string) => Paths + = (pkgName) => { + let WS = path.join(path.resolve(), 'packages', pkgName) + return { + benchDir: path.join(WS, PATHSPEC.BENCH_DIR), + tsconfig: path.join(WS, PATHSPEC.TSCONFIG), + tsconfigBench: path.join(WS, PATHSPEC.TSCONFIG_BENCH), + tsconfigTmp: path.join(WS, PATHSPEC.TSCONFIG_TMP), + } + } + +function runBenchmarks() { + try { Cmd.Terms() } + catch (_) { process.exit(0) } +} + +interface TsConfig { + references: { path: string }[] +} + +let hasPath = has('path', (u) => typeof u === 'string') +let isArrayOf + : (guard: (u: unknown) => u is T) => (xs: unknown) => xs is T[] + = (guard) => (xs): xs is never => Array.isArray(xs) && xs.every(guard) + +let References = isArrayOf(hasPath) + +let isTsConfig + : (u: unknown) => u is TsConfig + = has('references', References) + +function appendTsConfigBenchPathToTsConfig(filepath: string): TsConfig { + let tsconfig: TsConfig | undefined = void 0 + try { + let _ = JSON.parse(fs.readFileSync(filepath).toString('utf8')) + if (!isTsConfig(_)) + throw Error(`Expected '${PATHSPEC.TSCONFIG}' to match type \'TsConfig\' type.`) + else { + void (tsconfig = _) + } + } + catch (e) { + throw Error(`Could not parse '${PATHSPEC.TSCONFIG}' . Check to make sure the file is valid JSON.`) + } + finally { + if (!tsconfig) throw Error('Illegal state') + else if (!tsconfig.references.find(({ path }) => path === PATHSPEC.TSCONFIG_BENCH)) { + tsconfig.references.push({ path: PATHSPEC.TSCONFIG_BENCH }) + } + } + return tsconfig +} + +function unappendTsConfigBenchPathFromTsConfig(filepath: string): TsConfig { + let tsconfig: TsConfig | undefined = void 0 + try { + let _ = JSON.parse(fs.readFileSync(filepath).toString('utf8')) + if (!isTsConfig(_)) + throw Error(`Expected temporary 'tsconfig.json' file at '${PATHSPEC.TSCONFIG_TMP}' to match type \'TsConfig\' type.`) + else { + void (tsconfig = _) + } + } catch (e) { + throw Error(`Could not parse '${PATHSPEC.TSCONFIG_TMP}' . Check to make sure the file is valid JSON.`) + } finally { + if (!tsconfig) throw Error('Illegal state') + let tsconfigBenchIndex = tsconfig.references.findIndex(({ path }) => path === PATHSPEC.TSCONFIG_BENCH) + if (tsconfigBenchIndex === -1) { + return tsconfig + } else { + void (tsconfig.references.splice(tsconfigBenchIndex, 1)) + return tsconfig + } + } +} + +function prepareTypelevelBenchmarks(packages: string[]): void { + return void packages.forEach((pkgName) => { + let PATH = makePaths(pkgName) + if ( + fs.existsSync(PATH.benchDir) + && fs.existsSync(PATH.tsconfig) + && fs.existsSync(PATH.tsconfigBench) + ) { + let tsconfig = appendTsConfigBenchPathToTsConfig(PATH.tsconfig) + void LOG.onPrepare(pkgName, PATH) + void fs.rmSync(PATH.tsconfig) + void fs.writeFileSync(PATH.tsconfigTmp, JSON.stringify(tsconfig, null, 2)) + void fs.renameSync(PATH.tsconfigBench, PATH.tsconfig) + } + }) +} + +function cleanupTypelevelBenchmarks(packages: string[]) { + packages.forEach((pkgName) => { + let PATH = makePaths(pkgName) + if ( + fs.existsSync(PATH.benchDir) + && fs.existsSync(PATH.tsconfig) + && fs.existsSync(PATH.tsconfigTmp) + ) { + let tsconfig = unappendTsConfigBenchPathFromTsConfig(PATH.tsconfigTmp) + void LOG.onCleanup(pkgName, PATH) + void fs.rmSync(PATH.tsconfigTmp) + void fs.renameSync(PATH.tsconfig, PATH.tsconfigBench) + void fs.writeFileSync(PATH.tsconfig, JSON.stringify(tsconfig, null, 2)) + } + }) +} + +function runTypelevelBenchmarks(packages: string[]): void { + return void packages.forEach((pkgName) => { + let PATH = makePaths(pkgName) + if ( + fs.existsSync(PATH.benchDir) + && fs.existsSync(PATH.tsconfig) + && fs.existsSync(PATH.tsconfigTmp) + ) { + let packagePath = `${INIT_CWD}/packages/${pkgName}` + let filePaths = fs + .readdirSync(PATH.benchDir, { withFileTypes: true }) + .filter((dirent) => dirent.isFile() && dirent.name.endsWith(PATHSPEC.TYPELEVEL_BENCHMARK_SUFFIX)) + .map(({ parentPath, name }) => path.join(parentPath, name)) + .map((path) => path.startsWith(packagePath) ? '.' + path.slice(packagePath.length) : '.' + path) + return void filePaths.forEach((filePath) => { + void LOG.onRun(filePath) + try { Cmd.Types(pkgName, filePath) } + catch (e) { process.exit(1) } + }) + } + }) +} + +function main(arg: string) { + console.log('main, arg:', arg) + if (!isKeyOf(Script, arg)) { + throw Error('' + + `[bin/bench.ts]: bench script expected to receive command to run. Available commands:${NEWLINE}` + + Object.keys(Script).join(`${NEWLINE}${TAB} - `) + ) + } + else return void Script[arg]() +} + +main(arg) diff --git a/package.json b/package.json index 0da4e712..870cbbbe 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "private": true, "license": "MIT", "scripts": { - "bench": "pnpm vitest bench --outputJson benchmarks/benchmark--$(date -Iseconds).json", + "bench": "pnpm bench:types && pnpm bench:runtime", + "bench:runtime": "./bin/bench.ts run", + "bench:types": "./bin/bench.ts prepareTypes && ./bin/bench.ts runTypes && ./bin/bench.ts cleanupTypes", "boot": "pnpm install && pnpm reboot", "build": "pnpm build:root && pnpm run build:pre && pnpm --recursive --parallel run build && pnpm build:post", "build:docs": "pnpm dlx tsx ./bin/docs.ts", diff --git a/packages/schema-core/tsconfig.bench.json b/packages/schema-core/tsconfig.bench.json new file mode 100644 index 00000000..585bd382 --- /dev/null +++ b/packages/schema-core/tsconfig.bench.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "strict": true, + "tsBuildInfoFile": ".tsbuildinfo/bench.tsbuildinfo", + "rootDir": "bench", + "types": ["node"], + "noEmit": true, + // "paths": { + // "@traversable/registry": ["../../packages/registry/src/index.js"], + // "@traversable/registry/*": ["../../packages/registry/src/*.js"], + // "@traversable/schema-core": ["../../packages/schema-core/src/index.js"], + // "@traversable/schema-core/*": ["../../packages/schema-core/src/*.js"] + // } + }, + "references": [ + { "path": "tsconfig.src.json" }, + { "path": "../registry" }, + { "path": "../schema-core" }, + ], + "include": ["bench"] +} diff --git a/packages/schema-core/tsconfig.json b/packages/schema-core/tsconfig.json index 2c291d21..f4bb1bdf 100644 --- a/packages/schema-core/tsconfig.json +++ b/packages/schema-core/tsconfig.json @@ -2,7 +2,11 @@ "extends": "../../tsconfig.base.json", "include": [], "references": [ - { "path": "tsconfig.src.json" }, - { "path": "tsconfig.test.json" } + { + "path": "tsconfig.src.json" + }, + { + "path": "tsconfig.test.json" + } ] -} +} \ No newline at end of file From 6aa1a9de78e8248da94a2a3d5558340dad109a1a Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Mon, 21 Apr 2025 17:27:01 -0500 Subject: [PATCH 43/45] feat(schema): builds schemas from core + plugins --- bin/bench.ts | 160 +++++++-- bin/util.ts | 17 +- package.json | 44 +-- packages/schema-core/package.json | 7 +- .../src/__generated__/__manifest__.ts | 7 +- .../deep-object--no-baseline.bench.types.ts | 185 ++++++++++ .../test/types/deep-object.bench.types.ts | 191 ++++++++++ packages/schema-core/tsconfig.bench.json | 16 +- .../src/{build.ts => __build.ts__} | 19 +- packages/schema-generator/src/generate.ts | 29 +- packages/schema-generator/src/version.ts | 2 +- .../schema-generator/test/generate.test.ts | 1 + packages/schema-jit-compiler/src/shared.ts | 35 +- packages/schema/src/__schemas__/any.ts | 80 +++++ packages/schema/src/__schemas__/array.ts | 250 +++++++++++++ packages/schema/src/__schemas__/bigint.ts | 151 ++++++++ packages/schema/src/__schemas__/boolean.ts | 83 +++++ packages/schema/src/__schemas__/eq.ts | 122 +++++++ packages/schema/src/__schemas__/integer.ts | 172 +++++++++ packages/schema/src/__schemas__/intersect.ts | 147 ++++++++ packages/schema/src/__schemas__/never.ts | 80 +++++ packages/schema/src/__schemas__/null.ts | 86 +++++ packages/schema/src/__schemas__/number.ts | 210 +++++++++++ packages/schema/src/__schemas__/object.ts | 331 ++++++++++++++++++ packages/schema/src/__schemas__/of.ts | 94 +++++ packages/schema/src/__schemas__/optional.ts | 135 +++++++ packages/schema/src/__schemas__/record.ts | 160 +++++++++ packages/schema/src/__schemas__/string.ts | 173 +++++++++ packages/schema/src/__schemas__/symbol.ts | 83 +++++ packages/schema/src/__schemas__/tuple.ts | 225 ++++++++++++ packages/schema/src/__schemas__/undefined.ts | 83 +++++ packages/schema/src/__schemas__/union.ts | 144 ++++++++ packages/schema/src/__schemas__/unknown.ts | 80 +++++ packages/schema/src/__schemas__/void.ts | 85 +++++ packages/schema/src/_exports.ts | 2 +- packages/schema/src/_namespace.ts | 22 ++ packages/schema/src/build.ts | 37 +- packages/schema/src/exports.ts | 1 + packages/schema/src/index.ts | 2 + pnpm-lock.yaml | 184 +++++++--- pnpm-workspace.yaml | 19 + symbol.object | 0 42 files changed, 3789 insertions(+), 165 deletions(-) create mode 100644 packages/schema-core/test/types/deep-object--no-baseline.bench.types.ts create mode 100644 packages/schema-core/test/types/deep-object.bench.types.ts rename packages/schema-generator/src/{build.ts => __build.ts__} (97%) create mode 100644 packages/schema/src/__schemas__/any.ts create mode 100644 packages/schema/src/__schemas__/array.ts create mode 100644 packages/schema/src/__schemas__/bigint.ts create mode 100644 packages/schema/src/__schemas__/boolean.ts create mode 100644 packages/schema/src/__schemas__/eq.ts create mode 100644 packages/schema/src/__schemas__/integer.ts create mode 100644 packages/schema/src/__schemas__/intersect.ts create mode 100644 packages/schema/src/__schemas__/never.ts create mode 100644 packages/schema/src/__schemas__/null.ts create mode 100644 packages/schema/src/__schemas__/number.ts create mode 100644 packages/schema/src/__schemas__/object.ts create mode 100644 packages/schema/src/__schemas__/of.ts create mode 100644 packages/schema/src/__schemas__/optional.ts create mode 100644 packages/schema/src/__schemas__/record.ts create mode 100644 packages/schema/src/__schemas__/string.ts create mode 100644 packages/schema/src/__schemas__/symbol.ts create mode 100644 packages/schema/src/__schemas__/tuple.ts create mode 100644 packages/schema/src/__schemas__/undefined.ts create mode 100644 packages/schema/src/__schemas__/union.ts create mode 100644 packages/schema/src/__schemas__/unknown.ts create mode 100644 packages/schema/src/__schemas__/void.ts delete mode 100644 symbol.object diff --git a/bin/bench.ts b/bin/bench.ts index b98459ef..68144dc5 100755 --- a/bin/bench.ts +++ b/bin/bench.ts @@ -5,20 +5,47 @@ import { execSync } from 'node:child_process' import { PACKAGES as packagePaths } from 'bin/constants.js' -let TAB = ' '.repeat(4) -let NEWLINE = '\r\n' let CR = '\r' let INIT_CWD = process.env.INIT_CWD ?? path.resolve() let PACKAGES = packagePaths.map((path) => path.startsWith('packages/') ? path.slice('packages/'.length) : path) +let WS = { + NEWLINE: '\r\n', + 2: ' '.repeat(2), + 4: ' '.repeat(4), +} + let PATHSPEC = { - BENCH_DIR: 'bench', + BENCH_SOURCE_DIR: ['test', 'types'], + BENCH_TARGET_DIR: 'bench', TSCONFIG: 'tsconfig.json', TSCONFIG_BENCH: 'tsconfig.bench.json', TSCONFIG_TMP: 'tsconfig.tmp.json', TYPELEVEL_BENCHMARK_SUFFIX: '.bench.types.ts', } as const +let PATTERN = { + RESULTS_START: 'export declare let RESULTS: [', + RESULTS_END: ']', +} + +let esc = (xs: string) => { + let char: string | undefined = undefined + let chars = [...xs] + let out = '' + while ((char = chars.shift()) !== undefined) { + if (char === '[' || char === ']') out += `\\${char}` + else out += char + } + return char +} + +let REG_EXP = { + LIBRARY_NAME: /bench\(["'`](.+):.+"/g, + INSTANTIATION_COUNT: /\.types\s*\(\[(.+),\s*["'`]instantiations["'`]\]\)/g, + RESULTS: new RegExp(esc(PATTERN.RESULTS_START) + '([^]*?)' + esc(PATTERN.RESULTS_END), 'g'), +} + let [, , arg /* , ...worspaces */] = process.argv let exec = (cmd: string) => execSync(cmd, { stdio: 'inherit' }) @@ -36,18 +63,20 @@ let Script = { let LOG = { onPrepare: (pkgName: string, PATH: Paths) => { - console.group(`${NEWLINE}Preparing benchmark run for workspace: ${pkgName}${NEWLINE}`) - console.info(`${CR}${TAB}Temporarily moving... ${NEWLINE}${TAB} -> ${PATH.tsconfig}${NEWLINE}${TAB} -> ${PATH.tsconfigTmp}${NEWLINE}`) - console.info(`${CR}${TAB}Temporarily moving... ${NEWLINE}${TAB} -> ${PATH.tsconfigBench}${NEWLINE}${TAB} -> ${PATH.tsconfig}${NEWLINE}`) + console.group(`${WS.NEWLINE}Preparing benchmark run for workspace: ${pkgName}${WS.NEWLINE}`) + console.info(`${CR}${WS[4]}Temporarily moving... ${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfig}${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfigTmp}${WS.NEWLINE}`) + console.info(`${CR}${WS[4]}Temporarily moving... ${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfigBench}${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfig}${WS.NEWLINE}`) + console.info(`${CR}${WS[4]}Putting back... ${WS.NEWLINE}${WS[4]} -> ${PATH.benchSourceDir}${WS.NEWLINE}${WS[4]} -> ${PATH.benchTargetDir}${WS.NEWLINE}`) console.groupEnd() }, onRun: (filePath: string) => { console.info(`Running typelevel benchmark: ` + filePath) }, onCleanup: (pkgName: string, PATH: Paths) => { - console.group(`${NEWLINE}Cleaning up benchmark run for workspace: ${pkgName}${NEWLINE}`) - console.info(`${CR}${TAB}Putting back... ${NEWLINE}${TAB} -> ${PATH.tsconfig}${NEWLINE}${TAB} -> ${PATH.tsconfigBench}${NEWLINE}`) - console.info(`${CR}${TAB}Putting back... ${NEWLINE}${TAB} -> ${PATH.tsconfigTmp}${NEWLINE}${TAB} -> ${PATH.tsconfig}${NEWLINE}`) + console.group(`${WS.NEWLINE}Cleaning up benchmark run for workspace: ${pkgName}${WS.NEWLINE}`) + console.info(`${CR}${WS[4]}Putting back... ${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfig}${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfigBench}${WS.NEWLINE}`) + console.info(`${CR}${WS[4]}Putting back... ${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfigTmp}${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfig}${WS.NEWLINE}`) + console.info(`${CR}${WS[4]}Putting back... ${WS.NEWLINE}${WS[4]} -> ${PATH.benchTargetDir}${WS.NEWLINE}${WS[4]} -> ${PATH.benchSourceDir}${WS.NEWLINE}`) console.groupEnd() }, } @@ -60,7 +89,8 @@ let has && (guard ? guard(x[k as never]) : true) interface Paths { - benchDir: string + benchSourceDir: string + benchTargetDir: string tsconfig: string tsconfigBench: string tsconfigTmp: string @@ -71,7 +101,8 @@ let makePaths = (pkgName) => { let WS = path.join(path.resolve(), 'packages', pkgName) return { - benchDir: path.join(WS, PATHSPEC.BENCH_DIR), + benchSourceDir: path.join(WS, ...PATHSPEC.BENCH_SOURCE_DIR), + benchTargetDir: path.join(WS, PATHSPEC.BENCH_TARGET_DIR), tsconfig: path.join(WS, PATHSPEC.TSCONFIG), tsconfigBench: path.join(WS, PATHSPEC.TSCONFIG_BENCH), tsconfigTmp: path.join(WS, PATHSPEC.TSCONFIG_TMP), @@ -147,8 +178,7 @@ function prepareTypelevelBenchmarks(packages: string[]): void { return void packages.forEach((pkgName) => { let PATH = makePaths(pkgName) if ( - fs.existsSync(PATH.benchDir) - && fs.existsSync(PATH.tsconfig) + fs.existsSync(PATH.tsconfig) && fs.existsSync(PATH.tsconfigBench) ) { let tsconfig = appendTsConfigBenchPathToTsConfig(PATH.tsconfig) @@ -156,6 +186,7 @@ function prepareTypelevelBenchmarks(packages: string[]): void { void fs.rmSync(PATH.tsconfig) void fs.writeFileSync(PATH.tsconfigTmp, JSON.stringify(tsconfig, null, 2)) void fs.renameSync(PATH.tsconfigBench, PATH.tsconfig) + void fs.renameSync(PATH.benchSourceDir, PATH.benchTargetDir) } }) } @@ -164,48 +195,129 @@ function cleanupTypelevelBenchmarks(packages: string[]) { packages.forEach((pkgName) => { let PATH = makePaths(pkgName) if ( - fs.existsSync(PATH.benchDir) - && fs.existsSync(PATH.tsconfig) + fs.existsSync(PATH.tsconfig) && fs.existsSync(PATH.tsconfigTmp) ) { let tsconfig = unappendTsConfigBenchPathFromTsConfig(PATH.tsconfigTmp) void LOG.onCleanup(pkgName, PATH) - void fs.rmSync(PATH.tsconfigTmp) + void fs.renameSync(PATH.benchTargetDir, PATH.benchSourceDir) void fs.renameSync(PATH.tsconfig, PATH.tsconfigBench) + void fs.rmSync(PATH.tsconfigTmp) void fs.writeFileSync(PATH.tsconfig, JSON.stringify(tsconfig, null, 2)) } }) } -function runTypelevelBenchmarks(packages: string[]): void { +function runTypelevelBenchmarks(packages: string[]) { return void packages.forEach((pkgName) => { let PATH = makePaths(pkgName) if ( - fs.existsSync(PATH.benchDir) - && fs.existsSync(PATH.tsconfig) + fs.existsSync(PATH.tsconfig) && fs.existsSync(PATH.tsconfigTmp) ) { let packagePath = `${INIT_CWD}/packages/${pkgName}` + let benchTargetPath = PATH.benchTargetDir let filePaths = fs - .readdirSync(PATH.benchDir, { withFileTypes: true }) + .readdirSync(PATH.benchTargetDir, { withFileTypes: true }) .filter((dirent) => dirent.isFile() && dirent.name.endsWith(PATHSPEC.TYPELEVEL_BENCHMARK_SUFFIX)) .map(({ parentPath, name }) => path.join(parentPath, name)) .map((path) => path.startsWith(packagePath) ? '.' + path.slice(packagePath.length) : '.' + path) - return void filePaths.forEach((filePath) => { + + void filePaths.forEach((filePath) => { void LOG.onRun(filePath) try { Cmd.Types(pkgName, filePath) } catch (e) { process.exit(1) } }) + + void parseBenchFiles(benchTargetPath).forEach(({ content, filePath }) => { + console.log('\r\n\r\nfilePath:\r\n', filePath, '\r\n\r\n') + console.log('\r\n\r\ncontent:\r\n', content, '\r\n\r\n') + return fs.writeFileSync(filePath, content) + }) } }) } +let zip = (xs: T[], ys: T[]): [T, T][] => { + let out = Array.of<[T, T]>() + let len = Math.min(xs.length, ys.length) + for (let ix = 0; ix < len; ix++) { + let x = xs[ix] + let y = ys[ix] + out.push([x, y]) + } + return out +} + +let resultsComparator = ({ instantiations: l }: BenchResult, { instantiations: r }: BenchResult) => + Number.parseInt(l) < Number.parseInt(r) ? -1 + : Number.parseInt(r) < Number.parseInt(l) ? +1 + : 0 + +interface BenchResult { + libraryName: string + instantiations: string +} + +function createResults(benchResults: BenchResult[]) { + return `${PATTERN.RESULTS_START}${WS.NEWLINE}${benchResults.map(({ libraryName, instantiations }) => + `${WS[2]}{${WS.NEWLINE}${WS[4]}libraryName: "${libraryName}"${WS.NEWLINE}${WS[4]}instantiations: ${instantiations}${WS.NEWLINE}${WS[2]}}` + ).join(`,${WS.NEWLINE}`)}\r\n${PATTERN.RESULTS_END}` +} + +function parseBenchFile(benchFile: string): BenchResult[] { + let libs = benchFile.matchAll(REG_EXP.LIBRARY_NAME) + let results = benchFile.matchAll(REG_EXP.INSTANTIATION_COUNT) + if (libs === null) throw Error('parseBenchFile did not find any matches in libNames') + if (results === null) throw Error('parseBenchFile did not find any matches in instantiations') + let libraryNames = [...libs].map(([, libName]) => libName) + let counts = [...results].map(([, count]) => count) + let zipped = zip(libraryNames, counts) + return zipped + .map(([libraryName, instantiations]) => ({ libraryName, instantiations })) + .sort(resultsComparator) +} + +interface ParsedBenchFile { + filePath: string + content: string +} +function parseBenchFiles(dirpath: string): ParsedBenchFile[] { + let files = fs + .readdirSync(dirpath, { withFileTypes: true }) + let parsedFiles = files + .map(({ name, parentPath }) => path.join(parentPath, name)) + .map((filePath) => { + let originalContent = fs.readFileSync(filePath).toString('utf8') + let parsed = parseBenchFile(originalContent) + let index = originalContent.indexOf('bench.baseline') + let before_ = originalContent.slice(0, index).trim() + let after_ = originalContent.slice(index).trim() + let resultsStart = originalContent.indexOf(PATTERN.RESULTS_START) + let resultsEnd = resultsStart === -1 ? -1 : originalContent.indexOf(PATTERN.RESULTS_END, resultsStart) + let before = resultsStart === -1 ? before_ : originalContent.slice(0, resultsStart).trim() + let after = resultsStart === -1 ? after_ : originalContent.slice(resultsEnd + 1).trim() + return { + filePath, + content: '' + + before + + WS.NEWLINE + + WS.NEWLINE + + createResults(parsed) + + WS.NEWLINE + + WS.NEWLINE + + after + + WS.NEWLINE + } + }) + return parsedFiles +} + function main(arg: string) { - console.log('main, arg:', arg) if (!isKeyOf(Script, arg)) { throw Error('' - + `[bin/bench.ts]: bench script expected to receive command to run. Available commands:${NEWLINE}` - + Object.keys(Script).join(`${NEWLINE}${TAB} - `) + + `[bin/bench.ts]: bench script expected to receive command to run. Available commands:${WS.NEWLINE}` + + Object.keys(Script).join(`${WS.NEWLINE}${WS[4]} - `) ) } else return void Script[arg]() diff --git a/bin/util.ts b/bin/util.ts index 5d5e70d3..e33e7307 100644 --- a/bin/util.ts +++ b/bin/util.ts @@ -221,7 +221,7 @@ const prefix = `${REPO.scope}/` /** * @example * assert.equal( - * withoutPrefix("@traversable/core"), + * withoutPrefix("@traversable/schema-core"), * "core", * ) */ @@ -230,8 +230,8 @@ const withoutPrefix = (name: string) => name.substring(prefix.length) /** * @example * assert.equal( - * wrap("@traversable/core"), - * "core(@traversable/core)", + * wrap("@traversable/schema-core"), + * "core(@traversable/schema-core)", * ) */ const wrap = (name: string) => withoutPrefix(name).concat(`(${withoutPrefix(name)})`) @@ -239,8 +239,8 @@ const wrap = (name: string) => withoutPrefix(name).concat(`(${withoutPrefix(name /** * @example * assert.equal( - * bracket("@traversable/core"), - * "[@traversable/core](./packages/core)", + * bracket("@traversable/schema-core"), + * "[@traversable/schema-core](./packages/schema-core)", * ) */ const bracket = (name: string, version: string): `[${string}](./packages/${string})` => @@ -249,8 +249,8 @@ const bracket = (name: string, version: string): `[${string}](./packages/${strin /** * @example * assert.equal( - * drawRelation({ name: "@traversable/core" })("@traversable/data"), - * "core(@traversable/core) -.-> data(@traversable/data)", + * drawRelation({ name: "@traversable/schema-core" })("@traversable/data"), + * "core(@traversable/schema-core) -.-> data(@traversable/data)", * ) */ const drawRelation @@ -335,9 +335,6 @@ export const topological return graph } -// export const tap -// : (fn: (t: T) => U) => (t: T) => T -// = (fn) => (t) => (fn(t), t) export function tap(msg?: string): (x: T) => T export function tap(msg?: string | void): (x: T) => T export function tap(msg?: string, toString?: (x: T) => string): (x: T) => T diff --git a/package.json b/package.json index 870cbbbe..c11df0eb 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "bench:runtime": "./bin/bench.ts run", "bench:types": "./bin/bench.ts prepareTypes && ./bin/bench.ts runTypes && ./bin/bench.ts cleanupTypes", "boot": "pnpm install && pnpm reboot", - "build": "pnpm build:root && pnpm run build:pre && pnpm --recursive --parallel run build && pnpm build:post", + "build": "pnpm build:root && pnpm run build:pre && pnpm --recursive --parallel run build && pnpm build:post && pnpm build:dist && cd packages/schema && pnpm build:schemas", "build:docs": "pnpm dlx tsx ./bin/docs.ts", "build:pre": "pnpm dlx tsx ./bin/bump.ts", "build:pkgs": "pnpm --filter \"@traversable/*\" run \"/^build:.*/\"", @@ -39,30 +39,24 @@ "workspace:rm": "./bin/workspace-cleanup.ts" }, "devDependencies": { - "@ark/attest": "^0.44.8", - "@babel/cli": "^7.25.9", - "@babel/core": "^7.26.0", - "@babel/plugin-transform-export-namespace-from": "^7.25.9", - "@babel/plugin-transform-modules-commonjs": "^7.25.9", - "@changesets/changelog-github": "^0.5.0", - "@changesets/cli": "^2.27.9", - "@fast-check/vitest": "^0.2.0", - "@types/madge": "^5.0.3", - "@types/node": "^22.9.0", - "@vitest/coverage-v8": "3.1.1", - "@vitest/ui": "3.1.1", - "babel-plugin-annotate-pure-calls": "^0.4.0", - "fast-check": "^4.0.1", - "madge": "^8.0.0", - "tinybench": "^3.0.4", - "typescript": "5.8.2", - "vitest": "^3.0.4" - }, - "overrides": { - "typescript": "5.8.2" - }, - "resolutions": { - "typescript": "5.8.2" + "@ark/attest": "catalog:", + "@babel/cli": "catalog:", + "@babel/core": "catalog:", + "@babel/plugin-transform-export-namespace-from": "catalog:", + "@babel/plugin-transform-modules-commonjs": "catalog:", + "@changesets/changelog-github": "catalog:", + "@changesets/cli": "catalog:", + "@fast-check/vitest": "catalog:", + "@types/madge": "catalog:", + "@types/node": "catalog:", + "@vitest/coverage-v8": "catalog:", + "@vitest/ui": "catalog:", + "babel-plugin-annotate-pure-calls": "catalog:", + "fast-check": "catalog:", + "madge": "catalog:", + "tinybench": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:" }, "packageManager": "pnpm@10.2.1" } diff --git a/packages/schema-core/package.json b/packages/schema-core/package.json index e08f8a6b..32405daf 100644 --- a/packages/schema-core/package.json +++ b/packages/schema-core/package.json @@ -44,11 +44,16 @@ "test": "vitest" }, "devDependencies": { + "@sinclair/typebox": "^0.34.33", "@traversable/schema-zod-adapter": "workspace:^", "@types/lodash.isequal": "^4.5.8", + "arktype": "^2.1.20", "fast-check": "^3.0.0", "lodash.isequal": "^4.5.0", - "zod": "^3.24.2" + "valibot": "1.0.0-rc.1", + "zod": "^3.24.2", + "zod3": "npm:zod@3", + "zod4": "npm:zod@4.0.0-beta.20250420T053007" }, "peerDependencies": { "@traversable/registry": "workspace:^" diff --git a/packages/schema-core/src/__generated__/__manifest__.ts b/packages/schema-core/src/__generated__/__manifest__.ts index d4747eee..3cf973f6 100644 --- a/packages/schema-core/src/__generated__/__manifest__.ts +++ b/packages/schema-core/src/__generated__/__manifest__.ts @@ -39,11 +39,16 @@ export default { "test": "vitest" }, "devDependencies": { + "@sinclair/typebox": "^0.34.33", "@traversable/schema-zod-adapter": "workspace:^", "@types/lodash.isequal": "^4.5.8", + "arktype": "^2.1.20", "fast-check": "^3.0.0", "lodash.isequal": "^4.5.0", - "zod": "^3.24.2" + "valibot": "1.0.0-rc.1", + "zod": "^3.24.2", + "zod3": "npm:zod@3", + "zod4": "npm:zod@4.0.0-beta.20250420T053007" }, "peerDependencies": { "@traversable/registry": "workspace:^" diff --git a/packages/schema-core/test/types/deep-object--no-baseline.bench.types.ts b/packages/schema-core/test/types/deep-object--no-baseline.bench.types.ts new file mode 100644 index 00000000..aea753c2 --- /dev/null +++ b/packages/schema-core/test/types/deep-object--no-baseline.bench.types.ts @@ -0,0 +1,185 @@ +import { bench } from '@ark/attest' +import { t as core } from '@traversable/schema-core' +import { z as zod3 } from 'zod3' +import { z as zod4 } from 'zod4' +import { type as arktype } from 'arktype' +import { Type as typebox } from '@sinclair/typebox' +import * as valibot from 'valibot' + +export declare let RESULTS: [ + { + libraryName: "@traversable/schema" + instantiations: 1180 + }, + { + libraryName: "zod@4" + instantiations: 3328 + }, + { + libraryName: "@sinclair/typebox" + instantiations: 14320 + }, + { + libraryName: "arktype" + instantiations: 16235 + }, + { + libraryName: "valibot" + instantiations: 40168 + }, + { + libraryName: "zod@3" + instantiations: 40197 + } +] + +bench.baseline(() => void {}) + +bench("@traversable/schema: deep object", () => + core.object({ + a: core.object({ + b: core.object({ + c: core.optional( + core.object({ + d: core.boolean, + e: core.integer, + f: core.array( + core.object({ + g: core.unknown, + }), + ), + }), + ), + h: core.optional(core.record(core.string)), + }), + i: core.optional(core.bigint), + }), + j: core.optional( + core.object({ k: core.array(core.record(core.object({ l: core.string }))) }), + ), + }) +).types + ([1180, "instantiations"]) + +bench("zod@4: deep object", () => + zod4.object({ + a: zod4.object({ + b: zod4.object({ + c: zod4.optional( + zod4.object({ + d: zod4.boolean(), + e: zod4.number().int(), + f: zod4.array(zod4.object({ g: zod4.unknown() })), + }), + ), + h: zod4.optional(zod4.record(zod4.string(), zod4.string())), + }), + i: zod4.optional(zod4.bigint()), + }), + j: zod4.optional( + zod4.object({ + k: zod4.array(zod4.record(zod4.string(), zod4.object({ l: zod4.string() }))), + }), + ), + }) +).types + ([3328, "instantiations"]) + +bench("@sinclair/typebox: deep object", () => + typebox.Object({ + a: typebox.Object({ + b: typebox.Object({ + c: typebox.Optional( + typebox.Object({ + d: typebox.Boolean(), + e: typebox.Integer(), + f: typebox.Array(typebox.Object({ g: typebox.Unknown() })), + }), + ), + h: typebox.Optional(typebox.Record(typebox.String(), typebox.String())), + }), + i: typebox.Optional(typebox.BigInt()), + }), + j: typebox.Optional( + typebox.Object({ + k: typebox.Array( + typebox.Record(typebox.String(), typebox.Object({ l: typebox.String() })), + ), + }), + ), + }) +).types + ([14320, "instantiations"]) + +bench("arktype: deep object", () => + arktype({ + a: { + b: { + "c?": { + d: "boolean", + e: "number.integer", + f: arktype({ + g: "unknown", + }).array(), + }, + "h?": "Record", + }, + "i?": "bigint", + }, + "j?": { + k: arktype.Record("string", { l: "string" }).array(), + }, + }) +).types + ([16235, "instantiations"]) + +bench("valibot: deep object", () => + valibot.object({ + a: valibot.object({ + b: valibot.object({ + c: valibot.optional( + valibot.object({ + d: valibot.boolean(), + e: valibot.pipe(valibot.number(), valibot.integer()), + f: valibot.array( + valibot.object({ + g: valibot.unknown(), + }), + ), + }), + ), + h: valibot.optional(valibot.record(valibot.string(), valibot.string())), + }), + i: valibot.optional(valibot.bigint()), + }), + j: valibot.optional( + valibot.object({ + k: valibot.array(valibot.record(valibot.string(), valibot.object({ l: valibot.string() }))), + }), + ), + }) +).types + ([40168, "instantiations"]) + +bench("zod@3: deep object", () => zod3.object({ + a: zod3.object({ + b: zod3.object({ + c: zod3.optional( + zod3.object({ + d: zod3.boolean(), + e: zod3.number().int(), + f: zod3.array(zod3.object({ g: zod3.unknown() })), + }), + ), + h: zod3.optional(zod3.record(zod3.string(), zod3.string())), + }), + i: zod3.optional(zod3.bigint()), + }), + j: zod3.optional( + zod3.object({ + k: zod3.array(zod3.record(zod3.string(), zod3.object({ l: zod3.string() }))), + }), + ), +}) +).types + ([40197, "instantiations"]) diff --git a/packages/schema-core/test/types/deep-object.bench.types.ts b/packages/schema-core/test/types/deep-object.bench.types.ts new file mode 100644 index 00000000..ceb9f5b7 --- /dev/null +++ b/packages/schema-core/test/types/deep-object.bench.types.ts @@ -0,0 +1,191 @@ +import { bench } from '@ark/attest' +import { t as core } from '@traversable/schema-core' +import { z as zod3 } from 'zod3' +import { z as zod4 } from 'zod4' +import { type as arktype } from 'arktype' +import { Type as typebox } from '@sinclair/typebox' +import * as valibot from 'valibot' + +export declare let RESULTS: [ + { + libraryName: "@traversable/schema" + instantiations: 1149 + }, + { + libraryName: "zod@4" + instantiations: 3199 + }, + { + libraryName: "arktype" + instantiations: 12359 + }, + { + libraryName: "@sinclair/typebox" + instantiations: 14294 + }, + { + libraryName: "zod@3" + instantiations: 19292 + }, + { + libraryName: "valibot" + instantiations: 40067 + } +] + +bench.baseline(() => { + core.tuple(core.string) + zod3.tuple([zod3.string()]) + typebox.Tuple([typebox.String()]) + valibot.tuple([valibot.string()]) + arktype(['string']) +}) + +bench("@traversable/schema: deep object", () => + core.object({ + a: core.object({ + b: core.object({ + c: core.optional( + core.object({ + d: core.boolean, + e: core.integer, + f: core.array( + core.object({ + g: core.unknown, + }), + ), + }), + ), + h: core.optional(core.record(core.string)), + }), + i: core.optional(core.bigint), + }), + j: core.optional( + core.object({ k: core.array(core.record(core.object({ l: core.string }))) }), + ), + }) +).types + ([1149,"instantiations"]) + +bench("zod@4: deep object", () => + zod4.object({ + a: zod4.object({ + b: zod4.object({ + c: zod4.optional( + zod4.object({ + d: zod4.boolean(), + e: zod4.number().int(), + f: zod4.array(zod4.object({ g: zod4.unknown() })), + }), + ), + h: zod4.optional(zod4.record(zod4.string(), zod4.string())), + }), + i: zod4.optional(zod4.bigint()), + }), + j: zod4.optional( + zod4.object({ + k: zod4.array(zod4.record(zod4.string(), zod4.object({ l: zod4.string() }))), + }), + ), + }) +).types + ([3199,"instantiations"]) + +bench("arktype: deep object", () => + arktype({ + a: { + b: { + "c?": { + d: "boolean", + e: "number.integer", + f: arktype({ + g: "unknown", + }).array(), + }, + "h?": "Record", + }, + "i?": "bigint", + }, + "j?": { + k: arktype.Record("string", { l: "string" }).array(), + }, + }) +).types + ([12359,"instantiations"]) + +bench("@sinclair/typebox: deep object", () => + typebox.Object({ + a: typebox.Object({ + b: typebox.Object({ + c: typebox.Optional( + typebox.Object({ + d: typebox.Boolean(), + e: typebox.Integer(), + f: typebox.Array(typebox.Object({ g: typebox.Unknown() })), + }), + ), + h: typebox.Optional(typebox.Record(typebox.String(), typebox.String())), + }), + i: typebox.Optional(typebox.BigInt()), + }), + j: typebox.Optional( + typebox.Object({ + k: typebox.Array( + typebox.Record(typebox.String(), typebox.Object({ l: typebox.String() })), + ), + }), + ), + }) +).types + ([14294,"instantiations"]) + +bench("zod@3: deep object", () => zod3.object({ + a: zod3.object({ + b: zod3.object({ + c: zod3.optional( + zod3.object({ + d: zod3.boolean(), + e: zod3.number().int(), + f: zod3.array(zod3.object({ g: zod3.unknown() })), + }), + ), + h: zod3.optional(zod3.record(zod3.string(), zod3.string())), + }), + i: zod3.optional(zod3.bigint()), + }), + j: zod3.optional( + zod3.object({ + k: zod3.array(zod3.record(zod3.string(), zod3.object({ l: zod3.string() }))), + }), + ), +}) +).types + ([19292,"instantiations"]) + +bench("valibot: deep object", () => + valibot.object({ + a: valibot.object({ + b: valibot.object({ + c: valibot.optional( + valibot.object({ + d: valibot.boolean(), + e: valibot.pipe(valibot.number(), valibot.integer()), + f: valibot.array( + valibot.object({ + g: valibot.unknown(), + }), + ), + }), + ), + h: valibot.optional(valibot.record(valibot.string(), valibot.string())), + }), + i: valibot.optional(valibot.bigint()), + }), + j: valibot.optional( + valibot.object({ + k: valibot.array(valibot.record(valibot.string(), valibot.object({ l: valibot.string() }))), + }), + ), + }) +).types + ([40067,"instantiations"]) diff --git a/packages/schema-core/tsconfig.bench.json b/packages/schema-core/tsconfig.bench.json index 585bd382..fc2025ce 100644 --- a/packages/schema-core/tsconfig.bench.json +++ b/packages/schema-core/tsconfig.bench.json @@ -6,17 +6,15 @@ "rootDir": "bench", "types": ["node"], "noEmit": true, - // "paths": { - // "@traversable/registry": ["../../packages/registry/src/index.js"], - // "@traversable/registry/*": ["../../packages/registry/src/*.js"], - // "@traversable/schema-core": ["../../packages/schema-core/src/index.js"], - // "@traversable/schema-core/*": ["../../packages/schema-core/src/*.js"] - // } + "paths": { + "@traversable/registry": ["../../packages/registry/src/index.js"], + "@traversable/registry/*": ["../../packages/registry/src/*.js"], + "@traversable/schema-core": ["../../packages/schema-core/src/index.js"], + "@traversable/schema-core/*": ["../../packages/schema-core/src/*.js"] + } }, "references": [ - { "path": "tsconfig.src.json" }, - { "path": "../registry" }, - { "path": "../schema-core" }, + { "path": "tsconfig.src.json" } ], "include": ["bench"] } diff --git a/packages/schema-generator/src/build.ts b/packages/schema-generator/src/__build.ts__ similarity index 97% rename from packages/schema-generator/src/build.ts rename to packages/schema-generator/src/__build.ts__ index fc5f0000..cc92a028 100755 --- a/packages/schema-generator/src/build.ts +++ b/packages/schema-generator/src/__build.ts__ @@ -169,6 +169,7 @@ let BuildOptions = t.object({ * but won't write anything to disc. */ dryRun: t.optional(t.boolean), + pkgNameForHeader: t.string, /** * ## {@link BuildOptions.def.extensionFiles `Options.extensionFiles`} * @@ -217,8 +218,8 @@ type LibsConfig = never | { libs: Record } type ParseOptions = never | { [K in keyof T as K extends `get${infer P}` ? Uncapitalize

: K]-?: IfUnaryReturns } type BuildConfig = ParseOptions -interface Options extends BuildOptions, LibsOptions { } -interface Config extends BuildConfig, LibsConfig { } +interface Options extends BuildOptions, LibsOptions {} +interface Config extends BuildConfig, LibsConfig {} let defaultGetTargetFileName = ( (libName, _schemaName) => isKeyOf(libName, LIB_NAME_TO_TARGET_FILENAME) @@ -251,6 +252,7 @@ let defaultLibs = { let defaultOptions = { dryRun: false, + pkgNameForHeader: '', skipCleanup: false, postProcessor: defaultPostProcessor, excludeSchemas: null, @@ -300,6 +302,7 @@ function parseOptions({ extensionFiles, dryRun = defaultOptions.dryRun, excludeSchemas = null, + pkgNameForHeader = defaultOptions.pkgNameForHeader, getExtensionFilesDir = defaultOptions.getExtensionFilesDir, getNamespaceFile = defaultOptions.getNamespaceFile, getSourceDir = defaultOptions.getSourceDir, @@ -316,6 +319,7 @@ function parseOptions({ extensionFilesDir: getExtensionFilesDir(), libs: fn.map(libs, parseLibOptions), namespaceFile: getNamespaceFile(), + pkgNameForHeader, postProcessor, skipCleanup, sourceDir: getSourceDir(), @@ -457,8 +461,13 @@ function createTargetPaths($: Config, sourcePaths: Record path.join($.targetDir, `${schemaName}.ts`)) } -export function writeSchemas($: Config, sources: Record>, targets: Record): void { - let schemas = generateSchemas(sources, targets) +export function writeSchemas( + $: Config, + sources: Record>, + targets: Record, + pkgNameForHeader: string, +): void { + let schemas = generateSchemas(sources, targets, pkgNameForHeader) for (let [target, generatedContent] of schemas) { let pathSegments = target.split('/') let fileName = pathSegments[pathSegments.length - 1] @@ -541,7 +550,7 @@ function build(options: Options) { }) void ensureDir($.targetDir, $) - void writeSchemas($, sources, targets) + void writeSchemas($, sources, targets, $.pkgNameForHeader) void writeNamespaceFile($, sources) if ($.skipCleanup) { diff --git a/packages/schema-generator/src/generate.ts b/packages/schema-generator/src/generate.ts index 12e82639..b881eab7 100644 --- a/packages/schema-generator/src/generate.ts +++ b/packages/schema-generator/src/generate.ts @@ -12,14 +12,15 @@ import { replaceExtensions, } from './parser.js' import { makeImports } from './imports.js' +import { VERSION } from './version.js' let isKeyOf = (k: keyof any, t: T): k is keyof T => !!t && typeof t === 'object' && k in t -let makeSchemaFileHeader = (schemaName: string) => [ +let makeSchemaFileHeader = (schemaName: string, pkgName: string) => [ ` /** - * t.${schemaName} schema - * made with ᯓᡣ𐭩 by @traversable/schema + * t.${schemaName.endsWith('_') ? schemaName.slice(-1) : schemaName} schema + * made with ᯓᡣ𐭩 by ${pkgName} */ `.trim(), ].join('\n') @@ -39,6 +40,7 @@ function makeSchemaFileContent( parsedSourceFiles: Record, parsedExtensionFile: ParsedExtensionFile, imports: string, + pkgName: string, ) { let core = replaceExtensions(pick(parsedSourceFiles, 'core').core.body, parsedExtensionFile) let noCore = omit(parsedSourceFiles, 'core') @@ -53,7 +55,7 @@ function makeSchemaFileContent( ) return [ - makeSchemaFileHeader(schemaName), + makeSchemaFileHeader(schemaName, pkgName), imports, ...files.map((ext) => '\r' + ext), '\r', @@ -63,12 +65,14 @@ function makeSchemaFileContent( export function generateSchemas>>( sources: T, - targets: Record + targets: Record, + pkgNameForHeader: string, ): [path: string, content: string][] export function generateSchemas( sources: Record>, - targets: Record + targets: Record, + pkgNameForHeader: string, ): [path: string, content: string][] { let parsedSourceFiles = fn.map(sources, fn.map(parseFile)) let exts = fn.map(sources, (src) => pick(src, 'extension').extension) @@ -77,7 +81,13 @@ export function generateSchemas( let importsBySchemaName = makeImports(fn.map(parsedSourceFiles, fn.map((_) => _.imports))) let contentBySchemaName = fn.map( noExts, - (v, k) => makeSchemaFileContent(k, v, parsedExtensionFiles[k], importsBySchemaName[k]) + (v, k) => makeSchemaFileContent( + k, + v, + parsedExtensionFiles[k], + importsBySchemaName[k], + pkgNameForHeader, + ) ) return Object.entries(contentBySchemaName).map(([k, content]) => { @@ -86,8 +96,9 @@ export function generateSchemas( }) } -export function writeSchemas>>(sources: T, targets: Record): void -export function writeSchemas(...args: [sources: Record>, targets: Record]): void { +type WriteSchemasArgs = [sources: T, targets: Record, pkgNameForHeader: string] +export function writeSchemas>>(sources: WriteSchemasArgs[0], targets: WriteSchemasArgs[1], pkgNameForHeader: WriteSchemasArgs[2]): void +export function writeSchemas(...args: WriteSchemasArgs>>): void { let schemas = generateSchemas(...args) for (let [target, content] of schemas) { void fs.writeFileSync(target, content) diff --git a/packages/schema-generator/src/version.ts b/packages/schema-generator/src/version.ts index 388bbc3e..660ff1ca 100644 --- a/packages/schema-generator/src/version.ts +++ b/packages/schema-generator/src/version.ts @@ -1,3 +1,3 @@ import pkg from './__generated__/__manifest__.js' export const VERSION = `${pkg.name}@${pkg.version}` as const -export type VERSION = typeof VERSION \ No newline at end of file +export type VERSION = typeof VERSION diff --git a/packages/schema-generator/test/generate.test.ts b/packages/schema-generator/test/generate.test.ts index 2885e6bc..3d2c98c7 100644 --- a/packages/schema-generator/test/generate.test.ts +++ b/packages/schema-generator/test/generate.test.ts @@ -221,6 +221,7 @@ vi.describe.skip('〖️⛳️〗‹‹‹ ❲@traversable/schema-generator❳', writeSchemas( PATH.sources, PATH.targets, + '@traversable/test', ) }) }) diff --git a/packages/schema-jit-compiler/src/shared.ts b/packages/schema-jit-compiler/src/shared.ts index d47991e4..aac87a14 100644 --- a/packages/schema-jit-compiler/src/shared.ts +++ b/packages/schema-jit-compiler/src/shared.ts @@ -1,5 +1,5 @@ import type * as T from '@traversable/registry' -import { isValidIdentifier, parseKey } from '@traversable/registry' +import { fn, isValidIdentifier, parseKey } from '@traversable/registry' import { t } from '@traversable/schema-core' import type { Index } from './functor.js' @@ -30,24 +30,31 @@ export interface Free extends T.HKT { [-1]: F } export type Context = { VAR: string - // RETURN: string - // TABSTOP: string - // JOIN: string indent(numberOfSpaces: number): string dedent(numberOfSpaces: number): string join(numberOfSpaces: number): string } -export function buildContext(ix: T.Require): Context { - let VAR = ix.varName - let indent = (numberOfSpaces: number) => `\r${' '.repeat(Math.max(ix.offset + numberOfSpaces, 0))}` - let dedent = (numberOfSpaces: number) => `\r${' '.repeat(Math.max(ix.offset - numberOfSpaces, 0))}` - let join = (numberOfSpaces: number) => indent(numberOfSpaces) + '&& ' - // let JOIN = join(2) - // let RETURN = indent(2) - // let TABSTOP = indent(4) - return { dedent, indent, join, VAR } -} +export let makeIndent + : (offset: number) => (numberOfSpaces: number) => string + = (off) => (n) => `\r${' '.repeat(Math.max(off + n, 0))}` + +export let makeDedent + : (offset: number) => (numberOfSpaces: number) => string + = (off) => makeIndent(-off) + +export let makeJoin + : (offset: number) => (numberOfSpaces: number) => string + = (off) => fn.flow(makeIndent(off), (_) => `${_}&& `) + +export let buildContext + : (ix: T.Require) => Context + = ({ offset, varName: VAR }) => ({ + VAR, + indent: makeIndent(offset), + dedent: makeDedent(offset), + join: makeJoin(offset), + }) export function keyAccessor(key: keyof any | undefined, $: Index) { return typeof key === 'string' ? isValidIdentifier(key) ? $.isOptional diff --git a/packages/schema/src/__schemas__/any.ts b/packages/schema/src/__schemas__/any.ts new file mode 100644 index 00000000..701e54f2 --- /dev/null +++ b/packages/schema/src/__schemas__/any.ts @@ -0,0 +1,80 @@ +/** + * any_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../index.js' +import type { ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: unknown, right: unknown): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function unknownToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return unknownToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'any' } +export function toString(): 'any' { return 'any' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(_?: any_): validate { + validateAny.tag = URI.any + function validateAny() { return true as const } + return validateAny +} +/// validate /// +////////////////////// + +export { any_ as any } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface any_ extends any_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function AnySchema(src: unknown): src is any { return true } +AnySchema.tag = URI.any +AnySchema.def = void 0 as any + +const any_ = Object_assign( + AnySchema, + userDefinitions, +) as any_ + +Object_assign(any_, userExtensions) + +declare namespace any_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.any + _type: any + get def(): this['_type'] + } +} diff --git a/packages/schema/src/__schemas__/array.ts b/packages/schema/src/__schemas__/array.ts new file mode 100644 index 00000000..7f149e0d --- /dev/null +++ b/packages/schema/src/__schemas__/array.ts @@ -0,0 +1,250 @@ +/** + * array schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type * as T from '@traversable/registry' +import type { + Bounds, + Equal, + Integer, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + array as arrayOf, + Array_isArray, + bindUserExtensions, + carryover, + has, + Math_max, + Math_min, + Number_isSafeInteger, + Object_assign, + Object_is, + URI, + within +} from '@traversable/registry' +import type { Guarded, Schema, SchemaLike } from '../_namespace.js' +import type { of } from './of.js' +import type { t } from '../index.js' +import type { SizeBounds } from '@traversable/schema-to-json-schema' +import { hasSchema } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { Errors, NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal + +export function equals(arraySchema: array): equals +export function equals(arraySchema: array): equals +export function equals({ def }: array<{ equals: Equal }>): Equal { + let equals = has('equals', (x): x is Equal => typeof x === 'function')(def) ? def.equals : Object_is + function arrayEquals(l: unknown[], r: unknown[]): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + let len = l.length + if (len !== r.length) return false + for (let ix = len; ix-- !== 0;) + if (!equals(l[ix], r[ix])) return false + return true + } else return false + } + return arrayEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): never | T.Force< + & { type: 'array', items: T.Returns } + & T.PickIfDefined + > +} + +export function toJsonSchema>(arraySchema: T): toJsonSchema +export function toJsonSchema(arraySchema: T): toJsonSchema +export function toJsonSchema( + { def, minLength, maxLength }: { def: unknown, minLength?: number, maxLength?: number }, +): () => { + type: 'array' + items: unknown + minLength?: number + maxLength?: number +} { + function arrayToJsonSchema() { + let items = hasSchema(def) ? def.toJsonSchema() : def + let out = { + type: 'array' as const, + items, + minLength, + maxLength, + } + if (typeof minLength !== 'number') delete out.minLength + if (typeof maxLength !== 'number') delete out.maxLength + return out + } + return arrayToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType})[]` +} + +export function toString(arraySchema: array): toString +export function toString(arraySchema: array): toString +export function toString({ def }: { def: unknown }) { + function arrayToString() { + let body = ( + !!def + && typeof def === 'object' + && 'toString' in def + && typeof def.toString === 'function' + ) ? def.toString() + : '${string}' + return ('(' + body + ')[]') + } + return arrayToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = never | ValidationFn +export function validate(arraySchema: array): validate +export function validate(arraySchema: array): validate +export function validate( + { def: { validate = () => true }, minLength, maxLength }: array +) { + validateArray.tag = URI.array + function validateArray(u: unknown, path = Array.of()) { + if (!Array.isArray(u)) return [NullaryErrors.array(u, path)] + let errors = Array.of() + if (typeof minLength === 'number' && u.length < minLength) errors.push(Errors.arrayMinLength(u, path, minLength)) + if (typeof maxLength === 'number' && u.length > maxLength) errors.push(Errors.arrayMaxLength(u, path, maxLength)) + for (let i = 0, len = u.length; i < len; i++) { + let y = u[i] + let results = validate(y, [...path, i]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateArray +} +/// validate /// +////////////////////// + +/** @internal */ +function boundedArray(schema: S, bounds: Bounds, carry?: Partial>): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & array +function boundedArray(schema: S, bounds: Bounds, carry?: {}): ((u: unknown) => boolean) & Bounds & array { + return Object_assign(function BoundedArraySchema(u: unknown) { + return Array_isArray(u) && within(bounds)(u.length) + }, carry, array(schema)) +} + +export interface array extends array.core { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export function array(schema: S, readonly: 'readonly'): readonlyArray +export function array(schema: S): array +export function array(schema: S): array>> +export function array(schema: S): array { + return array.def(schema) +} + +export namespace array { + export let userDefinitions: Record = { + } as array + export function def(x: S, prev?: array): array + export function def(x: S, prev?: unknown): array + export function def(x: S, prev?: array): array + export function def(x: unknown, prev?: unknown): {} { + let userExtensions: Record = { + toJsonSchema, + validate, + toString, + equals, + } + const predicate = _isPredicate(x) ? arrayOf(x) : Array_isArray + function ArraySchema(src: unknown) { return predicate(src) } + ArraySchema.tag = URI.array + ArraySchema.def = x + ArraySchema.min = function arrayMin(minLength: Min) { + return Object_assign( + boundedArray(x, { gte: minLength }, carryover(this, 'minLength' as never)), + { minLength }, + ) + } + ArraySchema.max = function arrayMax(maxLength: Max) { + return Object_assign( + boundedArray(x, { lte: maxLength }, carryover(this, 'maxLength' as never)), + { maxLength }, + ) + } + ArraySchema.between = function arrayBetween( + min: Min, + max: Max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max) + ) { + return Object_assign( + boundedArray(x, { gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) + } + if (has('minLength', Number_isSafeInteger)(prev)) ArraySchema.minLength = prev.minLength + if (has('maxLength', Number_isSafeInteger)(prev)) ArraySchema.maxLength = prev.maxLength + Object_assign(ArraySchema, userDefinitions) + return Object_assign(ArraySchema, bindUserExtensions(ArraySchema, userExtensions)) + } +} + +export declare namespace array { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.array + get def(): S + _type: S['_type' & keyof S][] + minLength?: number + maxLength?: number + min>(minLength: Min): array.Min + max>(maxLength: Max): array.Max + between, Max extends Integer>(minLength: Min, maxLength: Max): array.between<[min: Min, max: Max], S> + } + type Min + = [Self] extends [{ maxLength: number }] + ? array.between<[min: Min, max: Self['maxLength']], Self['def' & keyof Self]> + : array.min + ; + type Max + = [Self] extends [{ minLength: number }] + ? array.between<[min: Self['minLength'], max: Max], Self['def' & keyof Self]> + : array.max + ; + interface min extends array { minLength: Min } + interface max extends array { maxLength: Max } + interface between extends array { minLength: Bounds[0], maxLength: Bounds[1] } + type type = never | T +} + +export const readonlyArray: { + (schema: S): readonlyArray + (schema: S): readonlyArray> +} = array +export interface readonlyArray { + (u: unknown): u is this['_type'] + tag: URI.array + def: S + _type: ReadonlyArray +} diff --git a/packages/schema/src/__schemas__/bigint.ts b/packages/schema/src/__schemas__/bigint.ts new file mode 100644 index 00000000..6c0e5940 --- /dev/null +++ b/packages/schema/src/__schemas__/bigint.ts @@ -0,0 +1,151 @@ +/** + * bigint_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Bounds, Equal, Unknown } from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Object_assign, + Object_is, + URI, + withinBig as within +} from '@traversable/registry' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' +import type { t } from '../index.js' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: bigint, right: bigint): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function bigintToJsonSchema(): void { + return void 0 + } + return bigintToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'bigint' } +export function toString(): 'bigint' { return 'bigint' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(bigIntSchema: S): validate { + validateBigInt.tag = URI.bigint + function validateBigInt(u: unknown, path = Array.of()): true | ValidationError[] { + return bigIntSchema(u) || [NullaryErrors.bigint(u, path)] + } + return validateBigInt +} +/// validate /// +////////////////////// + +export { bigint_ as bigint } + +/** @internal */ +function boundedBigInt(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & bigint_ +function boundedBigInt(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedBigIntSchema(u: unknown) { + return bigint_(u) && within(bounds)(u) + }, carry, bigint_) +} + +interface bigint_ extends bigint_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +function BigIntSchema(src: unknown) { return typeof src === 'bigint' } +BigIntSchema.tag = URI.bigint +BigIntSchema.def = 0n + +const bigint_ = Object_assign( + BigIntSchema, + userDefinitions, +) as bigint_ + +bigint_.min = function bigIntMin(minimum) { + return Object_assign( + boundedBigInt({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +bigint_.max = function bigIntMax(maximum) { + return Object_assign( + boundedBigInt({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +bigint_.between = function bigIntBetween( + min, + max, + minimum = (max < min ? max : min), + maximum = (max < min ? min : max), +) { + return Object_assign( + boundedBigInt({ gte: minimum, lte: maximum }), + { minimum, maximum } + ) +} + +Object_assign( + bigint_, + bindUserExtensions(bigint_, userExtensions), +) + +declare namespace bigint_ { + interface core extends bigint_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: bigint + tag: URI.bigint + get def(): this['_type'] + minimum?: bigint + maximum?: bigint + } + type Min + = [Self] extends [{ maximum: bigint }] + ? bigint_.between<[min: X, max: Self['maximum']]> + : bigint_.min + + type Max + = [Self] extends [{ minimum: bigint }] + ? bigint_.between<[min: Self['minimum'], max: X]> + : bigint_.max + + interface methods { + min(minimum: Min): bigint_.Min + max(maximum: Max): bigint_.Max + between( + minimum: Min, + maximum: Max + ): bigint_.between<[min: Min, max: Max]> + } + interface min extends bigint_ { minimum: Min } + interface max extends bigint_ { maximum: Max } + interface between extends bigint_ { minimum: Bounds[0], maximum: Bounds[1] } +} diff --git a/packages/schema/src/__schemas__/boolean.ts b/packages/schema/src/__schemas__/boolean.ts new file mode 100644 index 00000000..20af67b0 --- /dev/null +++ b/packages/schema/src/__schemas__/boolean.ts @@ -0,0 +1,83 @@ +/** + * boolean_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../index.js' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: boolean, right: boolean): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'boolean' } } +export function toJsonSchema(): toJsonSchema { + function booleanToJsonSchema() { return { type: 'boolean' as const } } + return booleanToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'boolean' } +export function toString(): 'boolean' { return 'boolean' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(booleanSchema: boolean_): validate { + validateBoolean.tag = URI.boolean + function validateBoolean(u: unknown, path = Array.of()) { + return booleanSchema(true as const) || [NullaryErrors.null(u, path)] + } + return validateBoolean +} +/// validate /// +////////////////////// + +export { boolean_ as boolean } + +interface boolean_ extends boolean_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +function BooleanSchema(src: unknown): src is boolean { return typeof src === 'boolean' } + +BooleanSchema.tag = URI.boolean +BooleanSchema.def = false + +const boolean_ = Object_assign( + BooleanSchema, + userDefinitions, +) as boolean_ + +Object_assign(boolean_, userExtensions) + +declare namespace boolean_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.boolean + _type: boolean + get def(): this['_type'] + } +} diff --git a/packages/schema/src/__schemas__/eq.ts b/packages/schema/src/__schemas__/eq.ts new file mode 100644 index 00000000..655eeac1 --- /dev/null +++ b/packages/schema/src/__schemas__/eq.ts @@ -0,0 +1,122 @@ +/** + * eq schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { + Key, + Mut, + Mutable, + SchemaOptions as Options, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + applyOptions, + bindUserExtensions, + Equal, + getConfig, + laxEquals, + Object_assign, + URI +} from '@traversable/registry' +import type { t } from '../index.js' +import { stringify } from '@traversable/schema-to-string' +import type { Validate } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal +export function equals(eqSchema: eq): equals +export function equals(): Equal { + return function eqEquals(left: any, right: any) { + return laxEquals(left, right) + } +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { const: T } } +export function toJsonSchema(eqSchema: eq): toJsonSchema +export function toJsonSchema({ def }: eq) { + function eqToJsonSchema() { return { const: def } } + return eqToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): [Key] extends [never] + ? [T] extends [symbol] ? 'symbol' : 'symbol' + : [T] extends [string] ? `'${T}'` : Key +} + +export function toString(eqSchema: eq): toString +export function toString({ def }: eq): () => string { + function eqToString(): string { + return typeof def === 'symbol' ? 'symbol' : stringify(def) + } + return eqToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate +export function validate(eqSchema: eq): validate +export function validate({ def }: eq): validate { + validateEq.tag = URI.eq + function validateEq(u: unknown, path = Array.of()) { + let options = getConfig().schema + let equals = options?.eq?.equalsFn || Equal.lax + if (equals(def, u)) return true + else return [Errors.eq(u, path, def)] + } + return validateEq +} +/// validate /// +////////////////////// + +export function eq>(value: V, options?: Options): eq> +export function eq(value: V, options?: Options): eq +export function eq(value: V, options?: Options): eq { + return eq.def(value, options) +} + +export interface eq extends eq.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace eq { + export let userDefinitions: Record = { + } + export function def(value: T, options?: Options): eq + export function def(x: T, $?: Options): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const options = applyOptions($) + const predicate = _isPredicate(x) ? x : (y: unknown) => options.eq.equalsFn(x, y) + function EqSchema(src: unknown) { return predicate(src) } + EqSchema.tag = URI.eq + EqSchema.def = x + Object_assign(EqSchema, eq.userDefinitions) + return Object_assign(EqSchema, bindUserExtensions(EqSchema, userExtensions)) + } +} + +export declare namespace eq { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.eq + _type: V + get def(): V + } +} diff --git a/packages/schema/src/__schemas__/integer.ts b/packages/schema/src/__schemas__/integer.ts new file mode 100644 index 00000000..5e31c890 --- /dev/null +++ b/packages/schema/src/__schemas__/integer.ts @@ -0,0 +1,172 @@ +/** + * integer schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { + Bounds, + Equal, + Force, + Integer, + PickIfDefined, + Unknown +} from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_max, + Math_min, + Number_isSafeInteger, + Object_assign, + SameValueNumber, + URI, + within +} from '@traversable/registry' +import type { t } from '../index.js' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): Force<{ type: 'integer' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: integer): toJsonSchema { + function integerToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'integer' as const, + ...bounds, + } + } + return integerToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(integerSchema: S): validate { + validateInteger.tag = URI.integer + function validateInteger(u: unknown, path = Array.of()): true | ValidationError[] { + return integerSchema(u) || [NullaryErrors.integer(u, path)] + } + return validateInteger +} +/// validate /// +////////////////////// + +export { integer } + +/** @internal */ +function boundedInteger(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & integer +function boundedInteger(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedIntegerSchema(u: unknown) { + return integer(u) && within(bounds)(u) + }, carry, integer) +} + +interface integer extends integer.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let userDefinitions: Record = { + equals, + toString, +} + +export let userExtensions: Record = { + toJsonSchema, + validate, +} + +function IntegerSchema(src: unknown) { return Number_isSafeInteger(src) } +IntegerSchema.tag = URI.integer +IntegerSchema.def = 0 + +const integer = Object_assign( + IntegerSchema, + userDefinitions, +) as integer + +integer.min = function integerMin(minimum) { + return Object_assign( + boundedInteger({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +integer.max = function integerMax(maximum) { + return Object_assign( + boundedInteger({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +integer.between = function integerBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedInteger({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + integer, + bindUserExtensions(integer, userExtensions), +) + +declare namespace integer { + interface core extends integer.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.integer + get def(): this['_type'] + minimum?: number + maximum?: number + } + interface methods { + min>(minimum: Min): integer.Min + max>(maximum: Max): integer.Max + between, const Max extends Integer>( + minimum: Min, + maximum: Max + ): integer.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maximum: number }] + ? integer.between<[min: X, max: Self['maximum']]> + : integer.min + type Max + = [Self] extends [{ minimum: number }] + ? integer.between<[min: Self['minimum'], max: X]> + : integer.max + interface min extends integer { minimum: Min } + interface max extends integer { maximum: Max } + interface between extends integer { minimum: Bounds[0], maximum: Bounds[1] } +} diff --git a/packages/schema/src/__schemas__/intersect.ts b/packages/schema/src/__schemas__/intersect.ts new file mode 100644 index 00000000..2fb8be06 --- /dev/null +++ b/packages/schema/src/__schemas__/intersect.ts @@ -0,0 +1,147 @@ +/** + * intersect schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { + Equal, + Join, + Returns, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + intersect as intersect$, + isUnknown as isAny, + Object_assign, + Object_is, + URI +} from '@traversable/registry' +import type { + Entry, + IntersectType, + Schema, + SchemaLike +} from '../_namespace.js' +import type { t } from '../index.js' +import { getSchema } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(intersectSchema: intersect<[...S]>): equals +export function equals(intersectSchema: intersect<[...S]>): equals +export function equals({ def }: intersect<{ equals: Equal }[]>): Equal { + function intersectEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (!def[ix].equals(l, r)) return false + return true + } + return intersectEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { + allOf: { [I in keyof T]: Returns } + } +} + +export function toJsonSchema(intersectSchema: intersect): toJsonSchema +export function toJsonSchema(intersectSchema: intersect): toJsonSchema +export function toJsonSchema({ def }: intersect): () => {} { + function intersectToJsonSchema() { + return { + allOf: def.map(getSchema) + } + } + return intersectToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): never | [T] extends [readonly []] ? 'unknown' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: Returns }, ' & '>})` +} + +export function toString(intersectSchema: intersect): toString +export function toString({ def }: intersect): () => string { + function intersectToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' & ')})` : 'unknown' + } + return intersectToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate + +export function validate(intersectSchema: intersect): validate +export function validate(intersectSchema: intersect): validate +export function validate({ def }: intersect) { + validateIntersect.tag = URI.intersect + function validateIntersect(u: unknown, path = Array.of()): true | ValidationError[] { + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results !== true) + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + return errors.length === 0 || errors + } + return validateIntersect +} +/// validate /// +////////////////////// + +export function intersect(...schemas: S): intersect +export function intersect }>(...schemas: S): intersect +export function intersect(...schemas: readonly unknown[]) { + return intersect.def(schemas) +} + +export interface intersect extends intersect.core { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export namespace intersect { + export let userDefinitions: Record = { + } as intersect + export function def(xs: readonly [...T]): intersect + export function def(xs: readonly unknown[]): {} { + let userExtensions: Record = { + toJsonSchema, + validate, + toString, + equals, + } + const predicate = xs.every(_isPredicate) ? intersect$(xs) : isAny + function IntersectSchema(src: unknown) { return predicate(src) } + IntersectSchema.tag = URI.intersect + IntersectSchema.def = xs + Object_assign(IntersectSchema, intersect.userDefinitions) + return Object_assign(IntersectSchema, bindUserExtensions(IntersectSchema, userExtensions)) + } +} + +export declare namespace intersect { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.intersect + get def(): S + _type: IntersectType + } + type type> = never | T +} diff --git a/packages/schema/src/__schemas__/never.ts b/packages/schema/src/__schemas__/never.ts new file mode 100644 index 00000000..ef051681 --- /dev/null +++ b/packages/schema/src/__schemas__/never.ts @@ -0,0 +1,80 @@ +/** + * never_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, URI } from '@traversable/registry' +import type { t } from '../index.js' +import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: never, right: never): boolean { + return false +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): never } +export function toJsonSchema(): toJsonSchema { + function neverToJsonSchema() { return void 0 as never } + return neverToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'never' } +export function toString(): 'never' { return 'never' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(_?: never_): validate { + validateNever.tag = URI.never + function validateNever(u: unknown, path = Array.of()) { return [NullaryErrors.never(u, path)] } + return validateNever +} +/// validate /// +////////////////////// + +export { never_ as never } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface never_ extends never_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function NeverSchema(src: unknown): src is never { return false } +NeverSchema.tag = URI.never; +NeverSchema.def = void 0 as never + +const never_ = Object_assign( + NeverSchema, + userDefinitions, +) as never_ + +Object_assign(never_, userExtensions) + +export declare namespace never_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.never + _type: never + get def(): this['_type'] + } +} diff --git a/packages/schema/src/__schemas__/null.ts b/packages/schema/src/__schemas__/null.ts new file mode 100644 index 00000000..4a7d423f --- /dev/null +++ b/packages/schema/src/__schemas__/null.ts @@ -0,0 +1,86 @@ +/** + * null_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../index.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: null, right: null): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'null', enum: [null] } } +export function toJsonSchema(): toJsonSchema { + function nullToJsonSchema() { return { type: 'null' as const, enum: [null] satisfies [any] } } + return nullToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'null' } +export function toString(): 'null' { return 'null' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(nullSchema: null_): validate { + validateNull.tag = URI.null + function validateNull(u: unknown, path = Array.of()) { + return nullSchema(u) || [NullaryErrors.null(u, path)] + } + return validateNull +} +/// validate /// +////////////////////// + +export { null_ as null, null_ } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface null_ extends null_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function NullSchema(src: unknown): src is null { return src === null } +NullSchema.def = null +NullSchema.tag = URI.null + +const null_ = Object_assign( + NullSchema, + userDefinitions, +) as null_ + +Object_assign( + null_, + userExtensions, +) + +declare namespace null_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.null + _type: null + get def(): this['_type'] + } +} diff --git a/packages/schema/src/__schemas__/number.ts b/packages/schema/src/__schemas__/number.ts new file mode 100644 index 00000000..9708d5b0 --- /dev/null +++ b/packages/schema/src/__schemas__/number.ts @@ -0,0 +1,210 @@ +/** + * number_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { + Bounds, + Equal, + Force, + PickIfDefined, + Unknown +} from '@traversable/registry' +import { + bindUserExtensions, + carryover, + Math_max, + Math_min, + Object_assign, + SameValueNumber, + URI, + within +} from '@traversable/registry' +import type { t } from '../index.js' +import type { NumericBounds } from '@traversable/schema-to-json-schema' +import { getNumericBounds } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: number, right: number): boolean { + return SameValueNumber(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): Force<{ type: 'number' } & PickIfDefined> } + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: number_): toJsonSchema { + function numberToJsonSchema() { + const { exclusiveMaximum, exclusiveMinimum, maximum, minimum } = getNumericBounds(schema) + let bounds: NumericBounds = {} + if (typeof exclusiveMinimum === 'number') bounds.exclusiveMinimum = exclusiveMinimum + if (typeof exclusiveMaximum === 'number') bounds.exclusiveMaximum = exclusiveMaximum + if (typeof minimum === 'number') bounds.minimum = minimum + if (typeof maximum === 'number') bounds.maximum = maximum + return { + type: 'number' as const, + ...bounds, + } + } + return numberToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'number' } +export function toString(): 'number' { return 'number' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(numberSchema: S): validate { + validateNumber.tag = URI.number + function validateNumber(u: unknown, path: (keyof any)[] = []): true | ValidationError[] { + return numberSchema(u) || [NullaryErrors.number(u, path)] + } + return validateNumber +} +/// validate /// +////////////////////// + +export { number_ as number } + +interface number_ extends number_.core { + toString: toString + equals: equals + toJsonSchema: toJsonSchema + validate: validate +} + +export let userDefinitions: Record = { + toString, + equals, +} + +export let userExtensions: Record = { + toJsonSchema, + validate, +} + +function NumberSchema(src: unknown) { return typeof src === 'number' } +NumberSchema.tag = URI.number +NumberSchema.def = 0 + +const number_ = Object_assign( + NumberSchema, + userDefinitions, +) as number_ + +number_.min = function numberMin(minimum) { + return Object_assign( + boundedNumber({ gte: minimum }, carryover(this, 'minimum')), + { minimum }, + ) +} +number_.max = function numberMax(maximum) { + return Object_assign( + boundedNumber({ lte: maximum }, carryover(this, 'maximum')), + { maximum }, + ) +} +number_.moreThan = function numberMoreThan(exclusiveMinimum) { + return Object_assign( + boundedNumber({ gt: exclusiveMinimum }, carryover(this, 'exclusiveMinimum')), + { exclusiveMinimum }, + ) +} +number_.lessThan = function numberLessThan(exclusiveMaximum) { + return Object_assign( + boundedNumber({ lt: exclusiveMaximum }, carryover(this, 'exclusiveMaximum')), + { exclusiveMaximum }, + ) +} +number_.between = function numberBetween( + min, + max, + minimum = Math_min(min, max), + maximum = Math_max(min, max), +) { + return Object_assign( + boundedNumber({ gte: minimum, lte: maximum }), + { minimum, maximum }, + ) +} + +Object_assign( + number_, + bindUserExtensions(number_, userExtensions), +) + +function boundedNumber(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & number_ +function boundedNumber(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedNumberSchema(u: unknown) { + return typeof u === 'number' && within(bounds)(u) + }, carry, number_) +} + +declare namespace number_ { + interface core extends number_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: number + tag: URI.number + get def(): this['_type'] + minimum?: number + maximum?: number + exclusiveMinimum?: number + exclusiveMaximum?: number + } + interface methods { + min(minimum: Min): number_.Min + max(maximum: Max): number_.Max + moreThan(moreThan: Min): ExclusiveMin + lessThan(lessThan: Max): ExclusiveMax + between( + minimum: Min, + maximum: Max + ): number_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.minStrictMax<[min: X, max: Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.between<[min: X, max: Self['maximum']]> + : number_.min + + type Max + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.maxStrictMin<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.between<[min: Self['minimum'], max: X]> + : number_.max + + type ExclusiveMin + = [Self] extends [{ exclusiveMaximum: number }] + ? number_.strictlyBetween<[X, Self['exclusiveMaximum']]> + : [Self] extends [{ maximum: number }] + ? number_.maxStrictMin<[min: X, Self['maximum']]> + : number_.moreThan + + type ExclusiveMax + = [Self] extends [{ exclusiveMinimum: number }] + ? number_.strictlyBetween<[Self['exclusiveMinimum'], X]> + : [Self] extends [{ minimum: number }] + ? number_.minStrictMax<[Self['minimum'], min: X]> + : number_.lessThan + + interface min extends number_ { minimum: Min } + interface max extends number_ { maximum: Max } + interface moreThan extends number_ { exclusiveMinimum: Min } + interface lessThan extends number_ { exclusiveMaximum: Max } + interface between extends number_ { minimum: Bounds[0], maximum: Bounds[1] } + interface minStrictMax extends number_ { minimum: Bounds[0], exclusiveMaximum: Bounds[1] } + interface maxStrictMin extends number_ { maximum: Bounds[1], exclusiveMinimum: Bounds[0] } + interface strictlyBetween extends number_ { exclusiveMinimum: Bounds[0], exclusiveMaximum: Bounds[1] } +} diff --git a/packages/schema/src/__schemas__/object.ts b/packages/schema/src/__schemas__/object.ts new file mode 100644 index 00000000..f71da03a --- /dev/null +++ b/packages/schema/src/__schemas__/object.ts @@ -0,0 +1,331 @@ +/** + * object_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type * as T from '@traversable/registry' +import type { + Force, + Join, + Returns, + SchemaOptions as Options, + UnionToTuple, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + applyOptions, + Array_isArray, + bindUserExtensions, + fn, + has, + isAnyObject, + object as object$, + Object_assign, + Object_hasOwn, + Object_is, + Object_keys, + record as record$, + symbol, + typeName, + URI +} from '@traversable/registry' +import type { + Entry, + Optional, + Required, + Schema, + SchemaLike +} from '../_namespace.js' +import type { t } from '../index.js' +import { getConfig } from '../index.js' +import type { RequiredKeys } from '@traversable/schema-to-json-schema' +import { isRequired, property } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { Errors, NullaryErrors, UnaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | T.Equal +export function equals(objectSchema: object_): equals> +export function equals(objectSchema: object_): equals> +export function equals({ def }: object_): equals> { + function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + for (const k in def) { + const lHas = Object_hasOwn(l, k) + const rHas = Object_hasOwn(r, k) + if (lHas) { + if (!rHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (rHas) { + if (!lHas) return false + if (!def[k].equals(l[k], r[k])) return false + } + if (!def[k].equals(l[k], r[k])) return false + } + return true + } + return objectEquals +} + +// export type equals = never | T.Equal +// export function equals(objectSchema: object_): equals> +// export function equals(objectSchema: object_): equals> +// export function equals({ def }: object_<{ [x: string]: { equals: T.Equal } }>): T.Equal<{ [x: string]: unknown }> { +// function objectEquals(l: { [x: string]: unknown }, r: { [x: string]: unknown }) { +// if (Object_is(l, r)) return true +// if (!l || typeof l !== 'object' || Array_isArray(l)) return false +// if (!r || typeof r !== 'object' || Array_isArray(r)) return false +// for (const k in def) { +// const lHas = Object_hasOwn(l, k) +// const rHas = Object_hasOwn(r, k) +// if (lHas) { +// if (!rHas) return false +// if (!def[k].equals(l[k], r[k])) return false +// } +// if (rHas) { +// if (!lHas) return false +// if (!def[k].equals(l[k], r[k])) return false +// } +// if (!def[k].equals(l[k], r[k])) return false +// } +// return true +// } +// return objectEquals +// } +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema = RequiredKeys> { + (): { + type: 'object' + required: { [I in keyof KS]: KS[I] & string } + properties: { [K in keyof T]: Returns } + } +} + +export function toJsonSchema(objectSchema: object_): toJsonSchema +export function toJsonSchema(objectSchema: object_): toJsonSchema +export function toJsonSchema({ def }: { def: { [x: string]: unknown } }): () => { type: 'object', required: string[], properties: {} } { + const required = Object_keys(def).filter(isRequired(def)) + function objectToJsonSchema() { + return { + type: 'object' as const, + required, + properties: fn.map(def, (v, k) => property(required)(v, k as number | string)), + } + } + return objectToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +/** @internal */ +type Symbol_optional = typeof Symbol_optional +const Symbol_optional: typeof symbol.optional = symbol.optional + +/** @internal */ +const hasOptionalSymbol = (u: unknown): u is { toString(): T } => + !!u && typeof u === 'function' + && Symbol_optional in u + && typeof u[Symbol_optional] === 'number' + +/** @internal */ +const hasToString = (x: unknown): x is { toString(): string } => + !!x && typeof x === 'function' && 'toString' in x && typeof x.toString === 'function' + +export interface toString> { + (): never + | [keyof T] extends [never] ? '{}' + /* @ts-expect-error */ + : `{ ${Join<{ [I in keyof _]: `'${_[I]}${T[_[I]] extends { [Symbol_optional]: any } ? `'?` : `'`}: ${ReturnType}` }, ', '>} }` +} + + +export function toString>(objectSchema: object_): toString +export function toString({ def }: object_) { + function objectToString() { + if (!!def && typeof def === 'object') { + const entries = Object.entries(def) + if (entries.length === 0) return '{}' + else return `{ ${entries.map(([k, x]) => `'${k}${hasOptionalSymbol(x) ? "'?" : "'" + }: ${hasToString(x) ? x.toString() : 'unknown' + }`).join(', ') + } }` + } + else return '{ [x: string]: unknown }' + } + + return objectToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +/** @internal */ +let isObject = (u: unknown): u is { [x: string]: unknown } => + !!u && typeof u === 'object' && !Array_isArray(u) + +/** @internal */ +let isKeyOf = (k: keyof any, u: T): k is keyof T => + !!u && (typeof u === 'function' || typeof u === 'object') && k in u + +/** @internal */ +let isOptional = has('tag', (tag) => tag === URI.optional) + + +export type validate = never | ValidationFn + +export function validate(objectSchema: object_): validate +export function validate(objectSchema: object_): validate +export function validate(objectSchema: object_): validate<{ [x: string]: unknown }> { + validateObject.tag = URI.object + function validateObject(u: unknown, path_ = Array.of()) { + // if (objectSchema(u)) return true + if (!isObject(u)) return [Errors.object(u, path_)] + let errors = Array.of() + let { schema: { optionalTreatment } } = getConfig() + let keys = Object_keys(objectSchema.def) + if (optionalTreatment === 'exactOptional') { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (Object_hasOwn(u, k) && u[k] === undefined) { + if (isOptional(objectSchema.def[k].validate)) { + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + let args = [u[k], path, tag] as never as [unknown, (keyof any)[]] + errors.push(NullaryErrors[tag](...args)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag as keyof typeof UnaryErrors].invalid(u[k], path)) + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + let tag = typeName(objectSchema.def[k].validate) + if (isKeyOf(tag, NullaryErrors)) { + errors.push(NullaryErrors[tag](u[k], path, tag)) + } + else if (isKeyOf(tag, UnaryErrors)) { + errors.push(UnaryErrors[tag].invalid(u[k], path)) + } + errors.push(...results) + } + else if (Object_hasOwn(u, k)) { + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + errors.push(...results) + continue + } else { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + } + } + else { + for (let i = 0, len = keys.length; i < len; i++) { + let k = keys[i] + let path = [...path_, k] + if (!Object_hasOwn(u, k)) { + if (!isOptional(objectSchema.def[k].validate)) { + errors.push(UnaryErrors.object.missing(u, path)) + continue + } + else { + if (!Object_hasOwn(u, k)) continue + if (isOptional(objectSchema.def[k].validate) && Object_hasOwn(u, k)) { + if (u[k] === undefined) continue + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let j = 0; j < results.length; j++) { + let result = results[j] + errors.push(result) + continue + } + } + } + } + let results = objectSchema.def[k].validate(u[k], path) + if (results === true) continue + for (let l = 0; l < results.length; l++) { + let result = results[l] + errors.push(result) + } + } + } + return errors.length === 0 || errors + } + + return validateObject +} +/// validate /// +////////////////////// + +export { object_ as object } + +function object_< + S extends { [x: string]: Schema }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_< + S extends { [x: string]: SchemaLike }, + T extends { [K in keyof S]: Entry } +>(schemas: S, options?: Options): object_ +function object_(schemas: S, options?: Options) { + return object_.def(schemas, options) +} + +interface object_ extends object_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +namespace object_ { + export let userDefinitions: Record = { + } as object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ + export function def(xs: T, $?: Options, opt?: string[]): object_ + export function def(xs: { [x: string]: unknown }, $?: Options, opt_?: string[]): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const keys = Object_keys(xs) + const opt = Array_isArray(opt_) ? opt_ : keys.filter((k) => has(symbol.optional)(xs[k])) + const req = keys.filter((k) => !has(symbol.optional)(xs[k])) + const predicate = !record$(_isPredicate)(xs) ? isAnyObject : object$(xs, applyOptions($)) + function ObjectSchema(src: unknown) { return predicate(src) } + ObjectSchema.tag = URI.object + ObjectSchema.def = xs + ObjectSchema.opt = opt + ObjectSchema.req = req + Object_assign(ObjectSchema, userDefinitions) + return Object_assign(ObjectSchema, bindUserExtensions(ObjectSchema, userExtensions)) + } +} + +declare namespace object_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: object_.type + tag: URI.object + get def(): S + opt: Optional // TODO: use object_.Opt? + req: Required // TODO: use object_.Req? + } + type Opt = symbol.optional extends keyof S[K] ? never : K + type Req = symbol.optional extends keyof S[K] ? K : never + type type = Force< + & { [K in keyof S as Opt]-?: S[K]['_type' & keyof S[K]] } + & { [K in keyof S as Req]+?: S[K]['_type' & keyof S[K]] } + > +} diff --git a/packages/schema/src/__schemas__/of.ts b/packages/schema/src/__schemas__/of.ts new file mode 100644 index 00000000..11d79efd --- /dev/null +++ b/packages/schema/src/__schemas__/of.ts @@ -0,0 +1,94 @@ +/** + * of schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Unknown } from '@traversable/registry' +import { Equal, Object_assign, URI } from '@traversable/registry' +import type { + Entry, + Guard, + Guarded, + SchemaLike +} from '../_namespace.js' +import type { t } from '../index.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: T, right: T): boolean { + return Equal.lax(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function inlineToJsonSchema(): void { + return void 0 + } + return inlineToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(inlineSchema: of): validate { + validateInline.tag = URI.inline + function validateInline(u: unknown, path = Array.of()) { + return inlineSchema(u) || [NullaryErrors.inline(u, path)] + } + return validateInline +} +/// validate /// +////////////////////// + +export interface of extends of.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export function of(typeguard: S): Entry +export function of(typeguard: S): of +export function of(typeguard: (Guard) & { tag?: URI.inline, def?: Guard }) { + typeguard.def = typeguard + return Object_assign(typeguard, of.prototype) +} + +export namespace of { + export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, + } + export let userExtensions: Record = { + validate, + } + export function def(guard: T): of + export function def(guard: T) { + function InlineSchema(src: unknown) { return guard(src) } + InlineSchema.tag = URI.inline + InlineSchema.def = guard + return InlineSchema + } +} + +export declare namespace of { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + _type: Guarded + tag: URI.inline + get def(): S + } + type type> = never | T +} diff --git a/packages/schema/src/__schemas__/optional.ts b/packages/schema/src/__schemas__/optional.ts new file mode 100644 index 00000000..d471cfab --- /dev/null +++ b/packages/schema/src/__schemas__/optional.ts @@ -0,0 +1,135 @@ +/** + * optional schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { + Equal, + Force, + Returns, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + bindUserExtensions, + has, + isUnknown as isAny, + Object_assign, + Object_is, + optional as optional$, + symbol, + URI +} from '@traversable/registry' +import type { Entry, Schema, SchemaLike } from '../_namespace.js' +import type { t } from '../index.js' +import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { Validate, ValidationFn, Validator } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal +export function equals(optionalSchema: optional): equals +export function equals(optionalSchema: optional): equals +export function equals({ def }: optional<{ equals: Equal }>): Equal { + return function optionalEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + return def.equals(l, r) + } +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +type Nullable = Force + +export interface toJsonSchema { + (): Nullable> + [symbol.optional]: number +} + +export function toJsonSchema(optionalSchema: optional): toJsonSchema +export function toJsonSchema({ def }: optional) { + function optionalToJsonSchema() { return getSchema(def) } + optionalToJsonSchema[symbol.optional] = wrapOptional(def) + return optionalToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + /* @ts-expect-error */ + (): never | `(${ReturnType} | undefined)` +} + +export function toString(optionalSchema: optional): toString +export function toString({ def }: optional): () => string { + function optionalToString(): string { + return '(' + callToString(def) + ' | undefined)' + } + return optionalToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate + +export function validate(optionalSchema: optional): validate +export function validate(optionalSchema: optional): validate +export function validate({ def }: optional): ValidationFn { + validateOptional.tag = URI.optional + validateOptional.optional = 1 + function validateOptional(u: unknown, path = Array.of()) { + if (u === void 0) return true + return def.validate(u, path) + } + return validateOptional +} +/// validate /// +////////////////////// + +export function optional(schema: S): optional +export function optional(schema: S): optional> +export function optional(schema: S): optional { return optional.def(schema) } + +export interface optional extends optional.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace optional { + export let userDefinitions: Record = { + } + export function def(x: T): optional + export function def(x: T) { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const predicate = _isPredicate(x) ? optional$(x) : isAny + function OptionalSchema(src: unknown) { return predicate(src) } + OptionalSchema.tag = URI.optional + OptionalSchema.def = x + OptionalSchema[symbol.optional] = 1 + Object_assign(OptionalSchema, { ...optional.userDefinitions, get def() { return x } }) + return Object_assign(OptionalSchema, bindUserExtensions(OptionalSchema, userExtensions)) + } + export const is + : (u: unknown) => u is optional + = has('tag', (u) => u === URI.optional) +} + +export declare namespace optional { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.optional + _type: undefined | S['_type' & keyof S] + def: S + [symbol.optional]: number + } + export type type = never | T +} diff --git a/packages/schema/src/__schemas__/record.ts b/packages/schema/src/__schemas__/record.ts new file mode 100644 index 00000000..4daba2ea --- /dev/null +++ b/packages/schema/src/__schemas__/record.ts @@ -0,0 +1,160 @@ +/** + * record schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type * as T from '@traversable/registry' +import type { Equal, Returns, Unknown } from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + isAnyObject, + Object_assign, + Object_hasOwn, + Object_is, + Object_keys, + record as record$, + URI +} from '@traversable/registry' +import type { Entry, Schema, SchemaLike } from '../_namespace.js' +import type { t } from '../index.js' +import { getSchema } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = never | Equal +export function equals(recordSchema: record): equals +export function equals(recordSchema: record): equals +export function equals({ def }: record<{ equals: Equal }>): Equal> { + function recordEquals(l: Record, r: Record): boolean { + if (Object_is(l, r)) return true + if (!l || typeof l !== 'object' || Array_isArray(l)) return false + if (!r || typeof r !== 'object' || Array_isArray(r)) return false + const lhs = Object_keys(l) + const rhs = Object_keys(r) + let len = lhs.length + let k: string + if (len !== rhs.length) return false + for (let ix = len; ix-- !== 0;) { + k = lhs[ix] + if (!Object_hasOwn(r, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + len = rhs.length + for (let ix = len; ix-- !== 0;) { + k = rhs[ix] + if (!Object_hasOwn(l, k)) return false + if (!(def.equals(l[k], r[k]))) return false + } + return true + } + return recordEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { + type: 'object' + additionalProperties: T.Returns + } +} + +export function toJsonSchema(recordSchema: record): toJsonSchema +export function toJsonSchema(recordSchema: record): toJsonSchema +export function toJsonSchema({ def }: { def: unknown }): () => { type: 'object', additionalProperties: unknown } { + return function recordToJsonSchema() { + return { + type: 'object' as const, + additionalProperties: getSchema(def), + } + } +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + /* @ts-expect-error */ + (): never | `Record}>` +} + +export function toString>(recordSchema: S): toString +export function toString(recordSchema: record): toString +export function toString({ def }: { def: unknown }): () => string { + function recordToString() { + return `Record` + } + return recordToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = never | ValidationFn +export function validate(recordSchema: record): validate +export function validate(recordSchema: record): validate +export function validate({ def: { validate = () => true } }: record) { + validateRecord.tag = URI.record + function validateRecord(u: unknown, path = Array.of()) { + if (!u || typeof u !== 'object' || Array_isArray(u)) return [NullaryErrors.record(u, path)] + let errors = Array.of() + let keys = Object_keys(u) + for (let k of keys) { + let y = u[k] + let results = validate(y, [...path, k]) + if (results === true) continue + else errors.push(...results) + } + return errors.length === 0 || errors + } + return validateRecord +} +/// validate /// +////////////////////// + +export function record(schema: S): record +export function record(schema: S): record> +export function record(schema: Schema) { + return record.def(schema) +} + +export interface record extends record.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace record { + export let userDefinitions: Record = { + } + export function def(x: T): record + export function def(x: unknown): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const predicate = _isPredicate(x) ? record$(x) : isAnyObject + function RecordSchema(src: unknown) { return predicate(src) } + RecordSchema.tag = URI.record + RecordSchema.def = x + Object_assign(RecordSchema, record.userDefinitions) + return Object_assign(RecordSchema, bindUserExtensions(RecordSchema, userExtensions)) + } +} + +export declare namespace record { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.record + get def(): S + _type: Record + } + export type type> = never | T +} diff --git a/packages/schema/src/__schemas__/string.ts b/packages/schema/src/__schemas__/string.ts new file mode 100644 index 00000000..9308dec2 --- /dev/null +++ b/packages/schema/src/__schemas__/string.ts @@ -0,0 +1,173 @@ +/** + * string_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { + Bounds, + Equal, + Force, + Integer, + PickIfDefined, + Unknown +} from '@traversable/registry' +import { + bindUserExtensions, + carryover, + has, + Math_max, + Math_min, + Object_assign, + URI, + within +} from '@traversable/registry' +import type { t } from '../index.js' +import type { SizeBounds } from '@traversable/schema-to-json-schema' +import type { ValidationError, ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: string, right: string): boolean { + return left === right +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): Force<{ type: 'string' } & PickIfDefined> +} + +export function toJsonSchema(schema: S): toJsonSchema +export function toJsonSchema(schema: string_): () => { type: 'string' } & Partial { + function stringToJsonSchema() { + const minLength = has('minLength', (u: any) => typeof u === 'number')(schema) ? schema.minLength : null + const maxLength = has('maxLength', (u: any) => typeof u === 'number')(schema) ? schema.maxLength : null + let out: { type: 'string' } & Partial = { type: 'string' } + minLength !== null && void (out.minLength = minLength) + maxLength !== null && void (out.maxLength = maxLength) + + return out + } + return stringToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'string' } +export function toString(): 'string' { return 'string' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(stringSchema: S): validate { + validateString.tag = URI.string + function validateString(u: unknown, path = Array.of()): true | ValidationError[] { + return stringSchema(u) || [NullaryErrors.number(u, path)] + } + return validateString +} +/// validate /// +////////////////////// + +export { string_ as string } + +/** @internal */ +function boundedString(bounds: Bounds, carry?: Partial): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: { [x: string]: unknown }): ((u: unknown) => boolean) & Bounds & string_ +function boundedString(bounds: Bounds, carry?: {}): {} { + return Object_assign(function BoundedStringSchema(u: unknown) { + return string_(u) && within(bounds)(u.length) + }, carry, string_) +} + +interface string_ extends string_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export let userDefinitions: Record = { + toString, + equals, +} + +export let userExtensions: Record = { + toJsonSchema, + validate, +} + +function StringSchema(src: unknown) { return typeof src === 'string' } +StringSchema.tag = URI.string +StringSchema.def = '' + +const string_ = Object_assign( + StringSchema, + userDefinitions, +) as string_ + +string_.min = function stringMinLength(minLength) { + return Object_assign( + boundedString({ gte: minLength }, carryover(this, 'minLength')), + { minLength }, + ) +} +string_.max = function stringMaxLength(maxLength) { + return Object_assign( + boundedString({ lte: maxLength }, carryover(this, 'maxLength')), + { maxLength }, + ) +} +string_.between = function stringBetween( + min, + max, + minLength = Math_min(min, max), + maxLength = Math_max(min, max)) { + return Object_assign( + boundedString({ gte: minLength, lte: maxLength }), + { minLength, maxLength }, + ) +} + +Object_assign( + string_, + bindUserExtensions(string_, userExtensions), +) + +declare namespace string_ { + interface core extends string_.methods { + (u: this['_type'] | Unknown): u is this['_type'] + _type: string + tag: URI.string + get def(): this['_type'] + } + interface methods { + minLength?: number + maxLength?: number + min>(minLength: Min): string_.Min + max>(maxLength: Max): string_.Max + between, const Max extends Integer>( + minLength: Min, + maxLength: Max + ): string_.between<[min: Min, max: Max]> + } + type Min + = [Self] extends [{ maxLength: number }] + ? string_.between<[min: Min, max: Self['maxLength']]> + : string_.min + + type Max + = [Self] extends [{ minLength: number }] + ? string_.between<[min: Self['minLength'], max: Max]> + : string_.max + + interface min extends string_ { minLength: Min } + interface max extends string_ { maxLength: Max } + interface between extends string_ { + minLength: Bounds[0] + maxLength: Bounds[1] + } +} diff --git a/packages/schema/src/__schemas__/symbol.ts b/packages/schema/src/__schemas__/symbol.ts new file mode 100644 index 00000000..04bb7b06 --- /dev/null +++ b/packages/schema/src/__schemas__/symbol.ts @@ -0,0 +1,83 @@ +/** + * symbol_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../index.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: symbol, right: symbol): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function symbolToJsonSchema() { return void 0 } + return symbolToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'symbol' } +export function toString(): 'symbol' { return 'symbol' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(symbolSchema: symbol_): validate { + validateSymbol.tag = URI.symbol + function validateSymbol(u: unknown, path = Array.of()) { + return symbolSchema(true as const) || [NullaryErrors.symbol(u, path)] + } + return validateSymbol +} +/// validate /// +////////////////////// + +export { symbol_ as symbol } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface symbol_ extends symbol_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function SymbolSchema(src: unknown): src is symbol { return typeof src === 'symbol' } +SymbolSchema.tag = URI.symbol +SymbolSchema.def = Symbol() + +const symbol_ = Object_assign( + SymbolSchema, + userDefinitions, +) as symbol_ + +Object_assign(symbol_, userExtensions) + +declare namespace symbol_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.symbol + _type: symbol + get def(): this['_type'] + } +} diff --git a/packages/schema/src/__schemas__/tuple.ts b/packages/schema/src/__schemas__/tuple.ts new file mode 100644 index 00000000..ae6f6413 --- /dev/null +++ b/packages/schema/src/__schemas__/tuple.ts @@ -0,0 +1,225 @@ +/** + * tuple schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { + Equal, + Join, + Returns, + SchemaOptions as Options, + TypeError, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + getConfig, + has, + Object_assign, + Object_hasOwn, + Object_is, + parseArgs, + symbol, + tuple as tuple$, + URI +} from '@traversable/registry' +import type { + Entry, + FirstOptionalItem, + invalid, + Schema, + SchemaLike, + TupleType, + ValidateTuple +} from '../_namespace.js' +import type { optional } from './optional.js' +import type { t } from '../index.js' +import type { MinItems } from '@traversable/schema-to-json-schema' +import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' +import { hasToString } from '@traversable/schema-to-string' +import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' +import { Errors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal + +export function equals(tupleSchema: tuple): equals +export function equals(tupleSchema: tuple): equals +export function equals(tupleSchema: tuple) { + function tupleEquals(l: typeof tupleSchema['_type'], r: typeof tupleSchema['_type']): boolean { + if (Object_is(l, r)) return true + if (Array_isArray(l)) { + if (!Array_isArray(r)) return false + for (let ix = tupleSchema.def.length; ix-- !== 0;) { + if (!Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) continue + if (Object_hasOwn(l, ix) && !Object_hasOwn(r, ix)) return false + if (!Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) return false + if (Object_hasOwn(l, ix) && Object_hasOwn(r, ix)) { + if (!tupleSchema.def[ix].equals(l[ix], r[ix])) return false + } + } + return true + } + return false + } + return tupleEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { + type: 'array', + items: { [I in keyof T]: Returns } + additionalItems: false + minItems: MinItems + maxItems: T['length' & keyof T] + } +} + +export function toJsonSchema(tupleSchema: tuple): toJsonSchema +export function toJsonSchema({ def }: tuple): () => { + type: 'array' + items: unknown + additionalItems: false + minItems?: {} + maxItems?: number +} { + function tupleToJsonSchema() { + let min = minItems(def) + let max = def.length + let items = applyTupleOptionality(def, { min, max }) + return { + type: 'array' as const, + additionalItems: false as const, + items, + minItems: min, + maxItems: max, + } + } + return tupleToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): never | `[${Join<{ + [I in keyof T]: `${ + /* @ts-expect-error */ + T[I] extends { [Symbol_optional]: any } ? `_?: ${ReturnType}` : ReturnType + }` + }, ', '>}]` +} + +export function toString(tupleSchema: tuple): toString +export function toString(tupleSchema: tuple): () => string { + let isOptional = has('tag', (tag) => tag === URI.optional) + function stringToString() { + return Array_isArray(tupleSchema.def) + ? `[${tupleSchema.def.map( + (x) => isOptional(x) + ? `_?: ${hasToString(x) ? x.toString() : 'unknown'}` + : hasToString(x) ? x.toString() : 'unknown' + ).join(', ')}]` : 'unknown[]' + } + return stringToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate +export function validate(tupleSchema: tuple<[...S]>): validate +export function validate(tupleSchema: tuple<[...S]>): validate +export function validate(tupleSchema: tuple<[...S]>): Validate { + validateTuple.tag = URI.tuple + let isOptional = has('tag', (tag) => tag === URI.optional) + function validateTuple(u: unknown, path = Array.of()) { + let errors = Array.of() + if (!Array_isArray(u)) return [Errors.array(u, path)] + for (let i = 0; i < tupleSchema.def.length; i++) { + if (!(i in u) && !(isOptional(tupleSchema.def[i].validate))) { + errors.push(Errors.missingIndex(u, [...path, i])) + continue + } + let results = tupleSchema.def[i].validate(u[i], [...path, i]) + if (results !== true) { + for (let j = 0; j < results.length; j++) errors.push(results[j]) + results.push(Errors.arrayElement(u[i], [...path, i])) + } + } + if (u.length > tupleSchema.def.length) { + for (let k = tupleSchema.def.length; k < u.length; k++) { + let excess = u[k] + errors.push(Errors.excessItems(excess, [...path, k])) + } + } + return errors.length === 0 || errors + } + return validateTuple +} +/// validate /// +////////////////////// + +export { tuple } + +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple }>(...args: [...schemas: tuple.validate, options: Options]): tuple, T>> +function tuple(...args: [...schemas: tuple.validate, options: Options]): tuple, S>> +function tuple }>(...schemas: tuple.validate): tuple, T>> +function tuple(...schemas: tuple.validate): tuple, S>> +function tuple(...args: [...SchemaLike[]] | [...SchemaLike[], Options]) { + return tuple.def(...parseArgs(getConfig().schema, args)) +} + +interface tuple extends tuple.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +namespace tuple { + export let userDefinitions: Record = { + } as tuple + export function def(xs: readonly [...T], $?: Options, opt_?: number): tuple + export function def(xs: readonly unknown[], $: Options = getConfig().schema, opt_?: number): {} { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const opt = opt_ || xs.findIndex(has(symbol.optional)) + const options = { + ...$, minLength: $.optionalTreatment === 'treatUndefinedAndOptionalAsTheSame' ? -1 : xs.findIndex(has(symbol.optional)) + } satisfies tuple.InternalOptions + const predicate = !xs.every(_isPredicate) ? Array_isArray : tuple$(xs, options) + function TupleSchema(src: unknown) { return predicate(src) } + TupleSchema.tag = URI.tuple + TupleSchema.def = xs + TupleSchema.opt = opt + Object_assign(TupleSchema, tuple.userDefinitions) + return Object_assign(TupleSchema, bindUserExtensions(TupleSchema, userExtensions)) + } +} + +declare namespace tuple { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.tuple + _type: TupleType + opt: FirstOptionalItem + def: S + } + type type> = never | T + type InternalOptions = { minLength?: number } + type validate = ValidateTuple> + + type from + = TypeError extends V[number] ? { [I in keyof V]: V[I] extends TypeError ? invalid> : V[I] } : T +} diff --git a/packages/schema/src/__schemas__/undefined.ts b/packages/schema/src/__schemas__/undefined.ts new file mode 100644 index 00000000..4f16e4af --- /dev/null +++ b/packages/schema/src/__schemas__/undefined.ts @@ -0,0 +1,83 @@ +/** + * undefined_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../index.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: undefined, right: undefined): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function undefinedToJsonSchema(): void { return void 0 } + return undefinedToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'undefined' } +export function toString(): 'undefined' { return 'undefined' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(undefinedSchema: undefined_): validate { + validateUndefined.tag = URI.undefined + function validateUndefined(u: unknown, path = Array.of()) { + return undefinedSchema(u) || [NullaryErrors.undefined(u, path)] + } + return validateUndefined +} +/// validate /// +////////////////////// + +export { undefined_ as undefined } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface undefined_ extends undefined_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function UndefinedSchema(src: unknown): src is undefined { return src === void 0 } +UndefinedSchema.tag = URI.undefined +UndefinedSchema.def = void 0 as undefined + +const undefined_ = Object_assign( + UndefinedSchema, + userDefinitions, +) as undefined_ + +Object_assign(undefined_, userExtensions) + +declare namespace undefined_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.undefined + _type: undefined + get def(): this['_type'] + } +} diff --git a/packages/schema/src/__schemas__/union.ts b/packages/schema/src/__schemas__/union.ts new file mode 100644 index 00000000..f7247ec6 --- /dev/null +++ b/packages/schema/src/__schemas__/union.ts @@ -0,0 +1,144 @@ +/** + * union schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { + Equal, + Join, + Returns, + Unknown +} from '@traversable/registry' +import { + _isPredicate, + Array_isArray, + bindUserExtensions, + isUnknown as isAny, + Object_assign, + Object_is, + union as union$, + URI +} from '@traversable/registry' +import type { Entry, Schema, SchemaLike } from '../_namespace.js' +import type { t } from '../index.js' +import { getSchema } from '@traversable/schema-to-json-schema' +import { callToString } from '@traversable/schema-to-string' +import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(unionSchema: union<[...S]>): equals +export function equals(unionSchema: union<[...S]>): equals +export function equals({ def }: union<{ equals: Equal }[]>): Equal { + function unionEquals(l: unknown, r: unknown): boolean { + if (Object_is(l, r)) return true + for (let ix = def.length; ix-- !== 0;) + if (def[ix].equals(l, r)) return true + return false + } + return unionEquals +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { + (): { anyOf: { [I in keyof T]: Returns } } +} + +export function toJsonSchema(unionSchema: union): toJsonSchema +export function toJsonSchema(unionSchema: union): toJsonSchema +export function toJsonSchema({ def }: union): () => {} { + return function unionToJsonSchema() { + return { + anyOf: def.map(getSchema) + } + } +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { + (): never | [T] extends [readonly []] ? 'never' + /* @ts-expect-error */ + : `(${Join<{ [I in keyof T]: ReturnType }, ' | '>})` +} + +export function toString(unionSchema: union): toString +export function toString({ def }: union): () => string { + function unionToString() { + return Array_isArray(def) ? def.length === 0 ? 'never' : `(${def.map(callToString).join(' | ')})` : 'unknown' + } + return unionToString +} +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = Validate + +export function validate(unionSchema: union): validate +export function validate(unionSchema: union): validate +export function validate({ def }: union) { + validateUnion.tag = URI.union + function validateUnion(u: unknown, path = Array.of()): true | ValidationError[] { + // if (this.def.every((x) => t.optional.is(x.validate))) validateUnion.optional = 1; + let errors = Array.of() + for (let i = 0; i < def.length; i++) { + let results = def[i].validate(u, path) + if (results === true) { + // validateUnion.optional = 0 + return true + } + for (let j = 0; j < results.length; j++) errors.push(results[j]) + } + // validateUnion.optional = 0 + return errors.length === 0 || errors + } + return validateUnion +} +/// validate /// +////////////////////// + +export function union(...schemas: S): union +export function union }>(...schemas: S): union +export function union(...schemas: unknown[]) { + return union.def(schemas) +} + +export interface union extends union.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +export namespace union { + export let userDefinitions: Record = { + } as Partial> + export function def(xs: T): union + export function def(xs: unknown[]) { + let userExtensions: Record = { + toString, + equals, + toJsonSchema, + validate, + } + const predicate = xs.every(_isPredicate) ? union$(xs) : isAny + function UnionSchema(src: unknown): src is unknown { return predicate(src) } + UnionSchema.tag = URI.union + UnionSchema.def = xs + Object_assign(UnionSchema, union.userDefinitions) + return Object_assign(UnionSchema, bindUserExtensions(UnionSchema, userExtensions)) + } +} + +export declare namespace union { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.union + _type: union.type + get def(): S + } + type type = never | T +} diff --git a/packages/schema/src/__schemas__/unknown.ts b/packages/schema/src/__schemas__/unknown.ts new file mode 100644 index 00000000..da8224ea --- /dev/null +++ b/packages/schema/src/__schemas__/unknown.ts @@ -0,0 +1,80 @@ +/** + * unknown_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../index.js' +import type { ValidationFn } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: any, right: any): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): { type: 'object', properties: {}, nullable: true } } +export function toJsonSchema(): toJsonSchema { + function anyToJsonSchema() { return { type: 'object', properties: {}, nullable: true } as const } + return anyToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'unknown' } +export function toString(): 'unknown' { return 'unknown' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(_?: unknown_): validate { + validateUnknown.tag = URI.unknown + function validateUnknown() { return true as const } + return validateUnknown +} +/// validate /// +////////////////////// + +export { unknown_ as unknown } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface unknown_ extends unknown_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function UnknownSchema(src: unknown): src is unknown { return true } +UnknownSchema.tag = URI.unknown +UnknownSchema.def = void 0 as unknown + +const unknown_ = Object_assign( + UnknownSchema, + userDefinitions, +) as unknown_ + +Object_assign(unknown_, userExtensions) + +declare namespace unknown_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.unknown + _type: unknown + get def(): this['_type'] + } +} diff --git a/packages/schema/src/__schemas__/void.ts b/packages/schema/src/__schemas__/void.ts new file mode 100644 index 00000000..c498fecc --- /dev/null +++ b/packages/schema/src/__schemas__/void.ts @@ -0,0 +1,85 @@ +/** + * void_ schema + * made with ᯓᡣ𐭩 by @traversable/schema@0.0.35 + */ +import type { Equal, Unknown } from '@traversable/registry' +import { Object_assign, Object_is, URI } from '@traversable/registry' +import type { t } from '../index.js' +import type { ValidationFn } from '@traversable/derive-validators' +import { NullaryErrors } from '@traversable/derive-validators' + //////////////////// +/// equals /// +export type equals = Equal +export function equals(left: void, right: void): boolean { + return Object_is(left, right) +} +/// equals /// +//////////////////// + ////////////////////////// +/// toJsonSchema /// +export interface toJsonSchema { (): void } +export function toJsonSchema(): toJsonSchema { + function voidToJsonSchema(): void { + return void 0 + } + return voidToJsonSchema +} +/// toJsonSchema /// +////////////////////////// + ////////////////////// +/// toString /// +export interface toString { (): 'void' } +export function toString(): 'void' { return 'void' } +/// toString /// +////////////////////// + ////////////////////// +/// validate /// +export type validate = ValidationFn +export function validate(voidSchema: void_): validate { + validateVoid.tag = URI.void + function validateVoid(u: unknown, path = Array.of()) { + return voidSchema(u) || [NullaryErrors.void(u, path)] + } + return validateVoid +} +/// validate /// +////////////////////// + +export { void_ as void, void_ } + +export let userDefinitions: Record = { + equals, + toJsonSchema, + toString, +} + +export let userExtensions: Record = { + validate, +} + +interface void_ extends void_.core { + equals: equals + toJsonSchema: toJsonSchema + toString: toString + validate: validate +} + +function VoidSchema(src: unknown): src is void { return src === void 0 } +VoidSchema.tag = URI.void +VoidSchema.def = void 0 as void + +const void_ = Object_assign( + VoidSchema, + userDefinitions, +) as void_ + +Object_assign(void_, userExtensions) + +declare namespace void_ { + interface core { + (u: this['_type'] | Unknown): u is this['_type'] + tag: URI.void + _type: void + get def(): this['_type'] + } +} diff --git a/packages/schema/src/_exports.ts b/packages/schema/src/_exports.ts index 6cbf2955..51b9c459 100644 --- a/packages/schema/src/_exports.ts +++ b/packages/schema/src/_exports.ts @@ -1,2 +1,2 @@ -export * as t from './_namespace.js' +export type * as t from './_namespace.js' export { getConfig } from '@traversable/schema-core' diff --git a/packages/schema/src/_namespace.ts b/packages/schema/src/_namespace.ts index 94ca1a4c..87a0d25c 100644 --- a/packages/schema/src/_namespace.ts +++ b/packages/schema/src/_namespace.ts @@ -12,3 +12,25 @@ export type { TupleType, ValidateTuple, } from '@traversable/schema-core/namespace' + +export { any } from './__schemas__/any.js' +export { array } from './__schemas__/array.js' +export { bigint } from './__schemas__/bigint.js' +export { boolean } from './__schemas__/boolean.js' +export { eq } from './__schemas__/eq.js' +export { integer } from './__schemas__/integer.js' +export { intersect } from './__schemas__/intersect.js' +export { never } from './__schemas__/never.js' +export { null } from './__schemas__/null.js' +export { number } from './__schemas__/number.js' +export { object } from './__schemas__/object.js' +export { of } from './__schemas__/of.js' +export { optional } from './__schemas__/optional.js' +export { record } from './__schemas__/record.js' +export { string } from './__schemas__/string.js' +export { symbol } from './__schemas__/symbol.js' +export { tuple } from './__schemas__/tuple.js' +export { undefined } from './__schemas__/undefined.js' +export { union } from './__schemas__/union.js' +export { unknown } from './__schemas__/unknown.js' +export { void } from './__schemas__/void.js' diff --git a/packages/schema/src/build.ts b/packages/schema/src/build.ts index 3d60face..ae98ab7e 100755 --- a/packages/schema/src/build.ts +++ b/packages/schema/src/build.ts @@ -6,29 +6,15 @@ import { fn } from '@traversable/registry' import { t } from '@traversable/schema-core' import { generateSchemas } from '@traversable/schema-generator' -/** - * ## TODO - * - * - [x] Pull the .ts files out of `@traversable/schema-core` - * - [x] Pull the .ts files out of `@traversable/derive-equals` - * - [x] Pull the .ts files out of `@traversable/schema-to-json-schema` - * - [x] Pull the .ts files out of `@traversable/derive-validators` - * - [x] Pull the .ts files out of `@traversable/schema-to-string` - * - [x] Read extension config files from `extensions` dir - * - [x] Allow local imports to pass through the parser - * - [x] Write generated schemas to namespace file so they can be used by other schemas - * - [x] Clean up the temp dir - * - [x] Configure the package.json file to export from `__schemas__` - */ +import { VERSION } from './version.js' let CWD = process.cwd() - let PATH = { libsDir: path.join(CWD, 'node_modules', '@traversable'), tempDir: path.join(CWD, 'src', 'temp'), extensionsDir: path.join(CWD, 'src', 'extensions'), - targetDir: path.join(CWD, 'src', '__schemas__'), namespaceFile: path.join(CWD, 'src', '_namespace.ts'), + targetDir: path.join(CWD, 'src', '__schemas__'), } let EXTENSION_FILES_IGNORE_LIST = [ @@ -323,8 +309,6 @@ function buildSchemas($: Config): void { ? schemaFile.name.slice(0, -'.ts'.length) : schemaFile.name - console.log('schemaName', schemaName) - let targetFilePath = path.join( $.tempDir, schemaName, @@ -387,11 +371,13 @@ function createTargetPaths($: Config, sourcePaths: Record>, targets: Record): void { - let schemas = generateSchemas(sources, targets) + let schemas = generateSchemas(sources, targets, VERSION) + let schemaNames = Array.of() for (let [target, generatedContent] of schemas) { let pathSegments = target.split('/') let fileName = pathSegments[pathSegments.length - 1] let schemaName = fileName.endsWith('.ts') ? fileName.slice(0, -'.ts'.length) : fileName + void (schemaNames.push(schemaName)) let content = $.postProcessor(generatedContent, schemaName) if ($.dryRun) { console.group('\n\n[[DRY_RUN]]:: `writeSchemas`') @@ -405,6 +391,7 @@ export function writeSchemas($: Config, sources: Record>) { + console.log('calling getNamespaceFileContent') let targetDirNames = $.targetDir.split('/') let targetDirName = targetDirNames[targetDirNames.length - 1] let lines = Object.keys(sources).map((schemaName) => `export { ${schemaName} } from './${targetDirName}/${schemaName}.js'`) @@ -412,11 +399,12 @@ function getNamespaceFileContent(previousContent: string, $: Config, sources: Re } export function writeNamespaceFile($: Config, sources: Record>) { - let content = getNamespaceFileContent(fs.readFileSync($.namespaceFile).toString('utf8'), $, sources) - if (content.includes('export {')) { + let namespaceFileContent = fs.readFileSync($.namespaceFile).toString('utf8') + let content = getNamespaceFileContent(namespaceFileContent, $, sources) + if (namespaceFileContent.includes('__schemas__')) { if ($.dryRun) { console.group('\n\n[[DRY_RUN]]:: `writeNamespaceFile`') - console.debug('\ntarget file already have term-level exports:\n', content) + console.debug('\ntarget file already has term-level exports:\n', content) console.groupEnd() } else { return void 0 @@ -471,7 +459,4 @@ function build(options: Options) { else void cleanupTempDir($) } -build({ - ...defaultOptions, - // skipCleanup: true, -}) +build(defaultOptions) diff --git a/packages/schema/src/exports.ts b/packages/schema/src/exports.ts index f28474c5..98b131c2 100644 --- a/packages/schema/src/exports.ts +++ b/packages/schema/src/exports.ts @@ -1 +1,2 @@ export * from './version.js' +export { getConfig } from '@traversable/schema-core' diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 410a4bcb..e572e968 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -1 +1,3 @@ export * from './exports.js' +export * as t from './_namespace.js' +export * from './_namespace.js' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a5f730af..caba3128 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,66 +4,120 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false -overrides: - typescript: 5.8.2 +catalogs: + default: + '@ark/attest': + specifier: ^0.44.8 + version: 0.44.8 + '@babel/cli': + specifier: ^7.25.9 + version: 7.27.0 + '@babel/core': + specifier: ^7.26.0 + version: 7.26.10 + '@babel/plugin-transform-export-namespace-from': + specifier: ^7.25.9 + version: 7.25.9 + '@babel/plugin-transform-modules-commonjs': + specifier: ^7.25.9 + version: 7.26.3 + '@changesets/changelog-github': + specifier: ^0.5.0 + version: 0.5.1 + '@changesets/cli': + specifier: ^2.27.9 + version: 2.28.1 + '@fast-check/vitest': + specifier: ^0.2.0 + version: 0.2.0 + '@types/madge': + specifier: ^5.0.3 + version: 5.0.3 + '@types/node': + specifier: ^22.9.0 + version: 22.14.0 + '@vitest/coverage-v8': + specifier: 3.1.1 + version: 3.1.1 + '@vitest/ui': + specifier: 3.1.1 + version: 3.1.1 + babel-plugin-annotate-pure-calls: + specifier: ^0.4.0 + version: 0.4.0 + fast-check: + specifier: ^4.0.1 + version: 4.1.0 + madge: + specifier: ^8.0.0 + version: 8.0.0 + tinybench: + specifier: ^3.0.4 + version: 3.1.1 + typescript: + specifier: 5.8.2 + version: 5.8.2 + vitest: + specifier: ^3.0.4 + version: 3.1.1 importers: .: devDependencies: '@ark/attest': - specifier: ^0.44.8 + specifier: 'catalog:' version: 0.44.8(typescript@5.8.2) '@babel/cli': - specifier: ^7.25.9 + specifier: 'catalog:' version: 7.27.0(@babel/core@7.26.10) '@babel/core': - specifier: ^7.26.0 + specifier: 'catalog:' version: 7.26.10 '@babel/plugin-transform-export-namespace-from': - specifier: ^7.25.9 + specifier: 'catalog:' version: 7.25.9(@babel/core@7.26.10) '@babel/plugin-transform-modules-commonjs': - specifier: ^7.25.9 + specifier: 'catalog:' version: 7.26.3(@babel/core@7.26.10) '@changesets/changelog-github': - specifier: ^0.5.0 + specifier: 'catalog:' version: 0.5.1 '@changesets/cli': - specifier: ^2.27.9 + specifier: 'catalog:' version: 2.28.1 '@fast-check/vitest': - specifier: ^0.2.0 + specifier: 'catalog:' version: 0.2.0(vitest@3.1.1) '@types/madge': - specifier: ^5.0.3 + specifier: 'catalog:' version: 5.0.3 '@types/node': - specifier: ^22.9.0 + specifier: 'catalog:' version: 22.14.0 '@vitest/coverage-v8': - specifier: 3.1.1 + specifier: 'catalog:' version: 3.1.1(vitest@3.1.1) '@vitest/ui': - specifier: 3.1.1 + specifier: 'catalog:' version: 3.1.1(vitest@3.1.1) babel-plugin-annotate-pure-calls: - specifier: ^0.4.0 + specifier: 'catalog:' version: 0.4.0(@babel/core@7.26.10) fast-check: - specifier: ^4.0.1 + specifier: 'catalog:' version: 4.1.0 madge: - specifier: ^8.0.0 + specifier: 'catalog:' version: 8.0.0(typescript@5.8.2) tinybench: - specifier: ^3.0.4 + specifier: 'catalog:' version: 3.1.1 typescript: - specifier: 5.8.2 + specifier: 'catalog:' version: 5.8.2 vitest: - specifier: ^3.0.4 + specifier: 'catalog:' version: 3.1.1(@types/node@22.14.0)(@vitest/ui@3.1.1)(msw@2.7.3(@types/node@22.14.0)(typescript@5.8.2))(yaml@2.7.1) bin: @@ -114,9 +168,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../../packages/registry/dist - '@traversable/schema-core': + '@traversable/schema': specifier: workspace:^ - version: link:../../packages/schema-core/dist + version: link:../../packages/schema/dist '@traversable/schema-seed': specifier: workspace:^ version: link:../../packages/schema-seed/dist @@ -283,21 +337,36 @@ importers: specifier: workspace:^ version: link:../registry/dist devDependencies: + '@sinclair/typebox': + specifier: ^0.34.33 + version: 0.34.33 '@traversable/schema-zod-adapter': specifier: workspace:^ version: link:../schema-zod-adapter/dist '@types/lodash.isequal': specifier: ^4.5.8 version: 4.5.8 + arktype: + specifier: ^2.1.20 + version: 2.1.20 fast-check: specifier: ^3.0.0 version: 3.23.2 lodash.isequal: specifier: ^4.5.0 version: 4.5.0 + valibot: + specifier: 1.0.0-rc.1 + version: 1.0.0-rc.1(typescript@5.8.2) zod: specifier: ^3.24.2 version: 3.24.2 + zod3: + specifier: npm:zod@3 + version: zod@3.24.2 + zod4: + specifier: npm:zod@4.0.0-beta.20250420T053007 + version: zod@4.0.0-beta.20250420T053007 publishDirectory: dist packages/schema-generator: @@ -425,7 +494,7 @@ packages: resolution: {integrity: sha512-q14L0pIUXthlIo05qAePRygumNv2gC4RRozrU91hb2f9mBhwbxOQ77oW9oZ7VgMtU4Xg7GwceL7ogOT02RdnkA==} hasBin: true peerDependencies: - typescript: 5.8.2 + typescript: '*' '@ark/fs@0.44.5': resolution: {integrity: sha512-cn1NzzFZBX3nj6snDGP4dH9VjTy6VlM2B0Ew1LJlXtOO85aqwcBDzav19AZ34Z1I7yaR241h1zWmC2X++katMw==} @@ -433,12 +502,18 @@ packages: '@ark/schema@0.44.4': resolution: {integrity: sha512-TsZTX+k5J7xsGABsFjVdRUNgViGDMLv73sikBM8JNxC4STe0suTuMNa1OJ/AFP2N+LpJ1zL9tdWlg28PRqAYhg==} + '@ark/schema@0.46.0': + resolution: {integrity: sha512-c2UQdKgP2eqqDArfBqQIJppxJHvNNXuQPeuSPlDML4rjw+f1cu0qAlzOG4b8ujgm9ctIDWwhpyw6gjG5ledIVQ==} + '@ark/util@0.44.4': resolution: {integrity: sha512-zLfNZrsq5Dq+8B0pHJwL/wD3xNBHb8FoP0FuPB455w7HpqVaqO5qPXvn+YoO8v1Y6pNwLVsM9vCIiO221LoODQ==} '@ark/util@0.44.5': resolution: {integrity: sha512-qomwswzzv1aBIEWEWLlts1iM1/nGWDDhSNTYKI4qlpYfFT4+sn3ufcFInjHyD54gHJxpQAhbymUzW3QEylyOSA==} + '@ark/util@0.46.0': + resolution: {integrity: sha512-JPy/NGWn/lvf1WmGCPw2VGpBg5utZraE84I7wli18EDF3p3zc/e9WolT35tINeZO3l7C77SjqRJeAUoT0CvMRg==} + '@babel/cli@7.27.0': resolution: {integrity: sha512-bZfxn8DRxwiVzDO5CEeV+7IqXeCkzI4yYnrQbpwjT76CUyossQc6RYE7n+xfm0/2k40lPaCpW0FhxYs7EBAetw==} engines: {node: '>=6.9.0'} @@ -1090,7 +1165,7 @@ packages: resolution: {integrity: sha512-scZwpc78cJS8dx6LMLemVHqnaAVbPWasx36TxYWUASA63hxPx5XbGbb6pe4cSpT8dWqhBtsPpHZoFTrM1aqx7A==} engines: {node: '>=18.0.0'} peerDependencies: - typescript: 5.8.2 + typescript: ^5.1.0 peerDependenciesMeta: typescript: optional: true @@ -1103,7 +1178,7 @@ packages: resolution: {integrity: sha512-6M1Lq2mrH5zfGN++ay+a2KzdPqOh2TB7n6wYPPXA0rxQan8c5NZCvDFF635KS65LSzZDB+2VfFTgoPBERdYkYg==} engines: {node: '>=18.0.0'} peerDependencies: - typescript: 5.8.2 + typescript: ^5.1.0 peerDependenciesMeta: typescript: optional: true @@ -1224,6 +1299,9 @@ packages: cpu: [x64] os: [win32] + '@sinclair/typebox@0.34.33': + resolution: {integrity: sha512-5HAV9exOMcXRUxo+9iYB5n09XxzCXnfy4VTNW4xnDv+FgjzAGY989C28BIdljKqmF+ZltUwujE3aossvcVtq6g==} + '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} @@ -1326,14 +1404,14 @@ packages: peerDependencies: '@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 - typescript: 5.8.2 + typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/parser@8.29.0': resolution: {integrity: sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: 5.8.2 + typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/scope-manager@8.29.0': resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==} @@ -1344,7 +1422,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: 5.8.2 + typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/types@8.29.0': resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==} @@ -1354,14 +1432,14 @@ packages: resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - typescript: 5.8.2 + typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/utils@8.29.0': resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: 5.8.2 + typescript: '>=4.8.4 <5.9.0' '@typescript-eslint/visitor-keys@8.29.0': resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==} @@ -1374,7 +1452,7 @@ packages: '@typescript/vfs@1.6.0': resolution: {integrity: sha512-hvJUjNVeBMp77qPINuUvYXj4FyWeeMMKZkxEATEU3hqBAQ7qdTBCUFT7Sp0Zu0faeEtFf+ldXxMEDr/bk73ISg==} peerDependencies: - typescript: 5.8.2 + typescript: '*' '@vitejs/plugin-react@4.3.4': resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} @@ -1443,6 +1521,9 @@ packages: '@web3-storage/multipart-parser@1.0.0': resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} + '@zod/core@0.8.1': + resolution: {integrity: sha512-djj8hPhxIHcG8ptxITaw/Bout5HJZ9NyRbKr95Eilqwt9R0kvITwUQGDU+n+MVdsBIka5KwztmZSLti22F+P0A==} + '@zxing/text-encoding@0.9.0': resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} @@ -1503,6 +1584,9 @@ packages: argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + arktype@2.1.20: + resolution: {integrity: sha512-IZCEEXaJ8g+Ijd59WtSYwtjnqXiwM8sWQ5EjGamcto7+HVN9eK0C4p0zDlCuAwWhpqr6fIBkxPuYDl4/Mcj/+Q==} + arktype@2.1.9: resolution: {integrity: sha512-bq46shcLpfop4D9acVQN/+quZ+hIGs4OUzoLq2vCaZLdkITOlWkfamBk9abMuC6fbgxW1fu/2PamcQgggWhTwQ==} @@ -1767,13 +1851,13 @@ packages: resolution: {integrity: sha512-pgN43/80MmWVSEi5LUuiVvO/0a9ss5V7fwVfrJ4QzAQRd3cwqU1SfWGXJFcNKUqoD5cS+uIovhw5t/0rSeC5Mw==} engines: {node: '>=18'} peerDependencies: - typescript: 5.8.2 + typescript: ^5.4.4 detective-vue2@2.2.0: resolution: {integrity: sha512-sVg/t6O2z1zna8a/UIV6xL5KUa2cMTQbdTIIvqNM0NIPswp52fe43Nwmbahzj3ww4D844u/vC2PYfiGLvD3zFA==} engines: {node: '>=18'} peerDependencies: - typescript: 5.8.2 + typescript: ^5.4.4 dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} @@ -2360,7 +2444,7 @@ packages: engines: {node: '>=18'} hasBin: true peerDependencies: - typescript: 5.8.2 + typescript: ^5.4.4 peerDependenciesMeta: typescript: optional: true @@ -2454,7 +2538,7 @@ packages: engines: {node: '>=18'} hasBin: true peerDependencies: - typescript: 5.8.2 + typescript: '>= 4.8.x' peerDependenciesMeta: typescript: optional: true @@ -3000,7 +3084,7 @@ packages: resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==} engines: {node: '>=18.12'} peerDependencies: - typescript: 5.8.2 + typescript: '>=4.8.4' ts-graphviz@2.1.6: resolution: {integrity: sha512-XyLVuhBVvdJTJr2FJJV2L1pc4MwSjMhcunRVgDE9k4wbb2ee7ORYnPewxMWUav12vxyfUM686MSGsqnVRIInuw==} @@ -3030,7 +3114,7 @@ packages: engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 - typescript: 5.8.2 + typescript: '>=4.8.4 <5.9.0' typescript@5.8.2: resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==} @@ -3081,7 +3165,7 @@ packages: valibot@1.0.0-rc.1: resolution: {integrity: sha512-bTHNpeeQ403xS7qGHF/tw3EC/zkZOU5VdkfIsmRDu1Sp+BJNTNCm6m5HlwOgyW/03lofP+uQiq3R+Poo9wiCEg==} peerDependencies: - typescript: 5.8.2 + typescript: '>=5' peerDependenciesMeta: typescript: optional: true @@ -3263,6 +3347,9 @@ packages: zod@3.24.2: resolution: {integrity: sha512-lY7CDW43ECgW9u1TcT3IoXHflywfVqDYze4waEz812jR/bZ8FHDsl7pFQoSZTz5N+2NqRXs8GBwnAwo3ZNxqhQ==} + zod@4.0.0-beta.20250420T053007: + resolution: {integrity: sha512-5pp8Q0PNDaNcUptGiBE9akyioJh3RJpagIxrLtAVMR9IxwcSZiOsJD/1/98CyhItdTlI2H91MfhhLzRlU+fifA==} + snapshots: '@ampproject/remapping@2.3.0': @@ -3289,10 +3376,16 @@ snapshots: dependencies: '@ark/util': 0.44.4 + '@ark/schema@0.46.0': + dependencies: + '@ark/util': 0.46.0 + '@ark/util@0.44.4': {} '@ark/util@0.44.5': {} + '@ark/util@0.46.0': {} + '@babel/cli@7.27.0(@babel/core@7.26.10)': dependencies: '@babel/core': 7.26.10 @@ -4125,6 +4218,8 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.39.0': optional: true + '@sinclair/typebox@0.34.33': {} + '@standard-schema/spec@1.0.0': {} '@tanstack/form-core@1.3.0': @@ -4201,7 +4296,7 @@ snapshots: '@types/madge@5.0.3': dependencies: - '@types/node': 22.14.0 + '@types/node': 20.17.30 '@types/node@12.20.55': {} @@ -4437,6 +4532,8 @@ snapshots: '@web3-storage/multipart-parser@1.0.0': {} + '@zod/core@0.8.1': {} + '@zxing/text-encoding@0.9.0': optional: true @@ -4490,6 +4587,11 @@ snapshots: argparse@2.0.1: {} + arktype@2.1.20: + dependencies: + '@ark/schema': 0.46.0 + '@ark/util': 0.46.0 + arktype@2.1.9: dependencies: '@ark/schema': 0.44.4 @@ -6227,3 +6329,7 @@ snapshots: optional: true zod@3.24.2: {} + + zod@4.0.0-beta.20250420T053007: + dependencies: + '@zod/core': 0.8.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4e81e10c..0393de29 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,3 +2,22 @@ packages: - examples/*/ - bin - packages/*/ +catalog: + '@ark/attest': ^0.44.8 + '@babel/cli': ^7.25.9 + '@babel/core': ^7.26.0 + '@babel/plugin-transform-export-namespace-from': ^7.25.9 + '@babel/plugin-transform-modules-commonjs': ^7.25.9 + '@changesets/changelog-github': ^0.5.0 + '@changesets/cli': ^2.27.9 + '@fast-check/vitest': ^0.2.0 + '@types/node': ^22.9.0 + '@types/madge': ^5.0.3 + '@vitest/coverage-v8': 3.1.1 + '@vitest/ui': 3.1.1 + babel-plugin-annotate-pure-calls: ^0.4.0 + fast-check: ^4.0.1 + typescript: 5.8.2 + vitest: ^3.0.4 + madge: ^8.0.0 + tinybench: ^3.0.4 diff --git a/symbol.object b/symbol.object deleted file mode 100644 index e69de29b..00000000 From 03ef95d702dedc559a2fcce879c73f891beb0ba5 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Mon, 21 Apr 2025 17:56:58 -0500 Subject: [PATCH 44/45] test(core): adds typelevel benchmark samples --- bin/bench.ts | 2 +- .../deep-object--no-baseline.bench.types.ts | 385 +++++++++--------- .../test/types/deep-object.bench.types.ts | 153 +++---- .../types/object--no-baseline.bench.types.ts | 84 ++++ .../test/types/object.bench.types.ts | 90 ++++ packages/schema-core/tsconfig.bench.json | 2 +- packages/schema-core/tsconfig.json | 36 +- 7 files changed, 491 insertions(+), 261 deletions(-) create mode 100644 packages/schema-core/test/types/object--no-baseline.bench.types.ts create mode 100644 packages/schema-core/test/types/object.bench.types.ts diff --git a/bin/bench.ts b/bin/bench.ts index 68144dc5..76a3c5fe 100755 --- a/bin/bench.ts +++ b/bin/bench.ts @@ -66,7 +66,7 @@ let LOG = { console.group(`${WS.NEWLINE}Preparing benchmark run for workspace: ${pkgName}${WS.NEWLINE}`) console.info(`${CR}${WS[4]}Temporarily moving... ${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfig}${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfigTmp}${WS.NEWLINE}`) console.info(`${CR}${WS[4]}Temporarily moving... ${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfigBench}${WS.NEWLINE}${WS[4]} -> ${PATH.tsconfig}${WS.NEWLINE}`) - console.info(`${CR}${WS[4]}Putting back... ${WS.NEWLINE}${WS[4]} -> ${PATH.benchSourceDir}${WS.NEWLINE}${WS[4]} -> ${PATH.benchTargetDir}${WS.NEWLINE}`) + console.info(`${CR}${WS[4]}Temporarily moving... ${WS.NEWLINE}${WS[4]} -> ${PATH.benchSourceDir}${WS.NEWLINE}${WS[4]} -> ${PATH.benchTargetDir}${WS.NEWLINE}`) console.groupEnd() }, onRun: (filePath: string) => { diff --git a/packages/schema-core/test/types/deep-object--no-baseline.bench.types.ts b/packages/schema-core/test/types/deep-object--no-baseline.bench.types.ts index aea753c2..ffa44ded 100644 --- a/packages/schema-core/test/types/deep-object--no-baseline.bench.types.ts +++ b/packages/schema-core/test/types/deep-object--no-baseline.bench.types.ts @@ -1,185 +1,200 @@ -import { bench } from '@ark/attest' -import { t as core } from '@traversable/schema-core' -import { z as zod3 } from 'zod3' -import { z as zod4 } from 'zod4' -import { type as arktype } from 'arktype' -import { Type as typebox } from '@sinclair/typebox' -import * as valibot from 'valibot' - -export declare let RESULTS: [ - { - libraryName: "@traversable/schema" - instantiations: 1180 - }, - { - libraryName: "zod@4" - instantiations: 3328 - }, - { - libraryName: "@sinclair/typebox" - instantiations: 14320 - }, - { - libraryName: "arktype" - instantiations: 16235 - }, - { - libraryName: "valibot" - instantiations: 40168 - }, - { - libraryName: "zod@3" - instantiations: 40197 - } -] - -bench.baseline(() => void {}) - -bench("@traversable/schema: deep object", () => - core.object({ - a: core.object({ - b: core.object({ - c: core.optional( - core.object({ - d: core.boolean, - e: core.integer, - f: core.array( - core.object({ - g: core.unknown, - }), - ), - }), - ), - h: core.optional(core.record(core.string)), - }), - i: core.optional(core.bigint), - }), - j: core.optional( - core.object({ k: core.array(core.record(core.object({ l: core.string }))) }), - ), - }) -).types - ([1180, "instantiations"]) - -bench("zod@4: deep object", () => - zod4.object({ - a: zod4.object({ - b: zod4.object({ - c: zod4.optional( - zod4.object({ - d: zod4.boolean(), - e: zod4.number().int(), - f: zod4.array(zod4.object({ g: zod4.unknown() })), - }), - ), - h: zod4.optional(zod4.record(zod4.string(), zod4.string())), - }), - i: zod4.optional(zod4.bigint()), - }), - j: zod4.optional( - zod4.object({ - k: zod4.array(zod4.record(zod4.string(), zod4.object({ l: zod4.string() }))), - }), - ), - }) -).types - ([3328, "instantiations"]) - -bench("@sinclair/typebox: deep object", () => - typebox.Object({ - a: typebox.Object({ - b: typebox.Object({ - c: typebox.Optional( - typebox.Object({ - d: typebox.Boolean(), - e: typebox.Integer(), - f: typebox.Array(typebox.Object({ g: typebox.Unknown() })), - }), - ), - h: typebox.Optional(typebox.Record(typebox.String(), typebox.String())), - }), - i: typebox.Optional(typebox.BigInt()), - }), - j: typebox.Optional( - typebox.Object({ - k: typebox.Array( - typebox.Record(typebox.String(), typebox.Object({ l: typebox.String() })), - ), - }), - ), - }) -).types - ([14320, "instantiations"]) - -bench("arktype: deep object", () => - arktype({ - a: { - b: { - "c?": { - d: "boolean", - e: "number.integer", - f: arktype({ - g: "unknown", - }).array(), - }, - "h?": "Record", - }, - "i?": "bigint", - }, - "j?": { - k: arktype.Record("string", { l: "string" }).array(), - }, - }) -).types - ([16235, "instantiations"]) - -bench("valibot: deep object", () => - valibot.object({ - a: valibot.object({ - b: valibot.object({ - c: valibot.optional( - valibot.object({ - d: valibot.boolean(), - e: valibot.pipe(valibot.number(), valibot.integer()), - f: valibot.array( - valibot.object({ - g: valibot.unknown(), - }), - ), - }), - ), - h: valibot.optional(valibot.record(valibot.string(), valibot.string())), - }), - i: valibot.optional(valibot.bigint()), - }), - j: valibot.optional( - valibot.object({ - k: valibot.array(valibot.record(valibot.string(), valibot.object({ l: valibot.string() }))), - }), - ), - }) -).types - ([40168, "instantiations"]) - -bench("zod@3: deep object", () => zod3.object({ - a: zod3.object({ - b: zod3.object({ - c: zod3.optional( - zod3.object({ - d: zod3.boolean(), - e: zod3.number().int(), - f: zod3.array(zod3.object({ g: zod3.unknown() })), - }), - ), - h: zod3.optional(zod3.record(zod3.string(), zod3.string())), - }), - i: zod3.optional(zod3.bigint()), - }), - j: zod3.optional( - zod3.object({ - k: zod3.array(zod3.record(zod3.string(), zod3.object({ l: zod3.string() }))), - }), - ), -}) -).types - ([40197, "instantiations"]) +import { bench } from "@ark/attest" +import { t as core } from "@traversable/schema-core" +import { z as zod3 } from "zod3" +import { z as zod4 } from "zod4" +import { type as arktype } from "arktype" +import { Type as typebox } from "@sinclair/typebox" +import * as valibot from "valibot" + +export declare let RESULTS: [ + { + libraryName: "@traversable/schema" + instantiations: 1180 + }, + { + libraryName: "zod@4" + instantiations: 3328 + }, + { + libraryName: "@sinclair/typebox" + instantiations: 14320 + }, + { + libraryName: "arktype" + instantiations: 16235 + }, + { + libraryName: "valibot" + instantiations: 40168 + }, + { + libraryName: "zod@3" + instantiations: 40197 + } +] + +bench.baseline(() => void {}) + +bench("@traversable/schema: deep object (no baseline)", () => + core.object({ + a: core.object({ + b: core.object({ + c: core.optional( + core.object({ + d: core.boolean, + e: core.integer, + f: core.array( + core.object({ + g: core.unknown, + }), + ), + }), + ), + h: core.optional(core.record(core.string)), + }), + i: core.optional(core.bigint), + }), + j: core.optional( + core.object({ + k: core.array(core.record(core.object({ l: core.string }))), + }), + ), + }), +).types + ([1180, "instantiations"]) + +bench("zod@4: deep object (no baseline)", () => + zod4.object({ + a: zod4.object({ + b: zod4.object({ + c: zod4.optional( + zod4.object({ + d: zod4.boolean(), + e: zod4.number().int(), + f: zod4.array(zod4.object({ g: zod4.unknown() })), + }), + ), + h: zod4.optional(zod4.record(zod4.string(), zod4.string())), + }), + i: zod4.optional(zod4.bigint()), + }), + j: zod4.optional( + zod4.object({ + k: zod4.array( + zod4.record(zod4.string(), zod4.object({ l: zod4.string() })), + ), + }), + ), + }), +).types + ([3328, "instantiations"]) + +bench("@sinclair/typebox: deep object (no baseline)", () => + typebox.Object({ + a: typebox.Object({ + b: typebox.Object({ + c: typebox.Optional( + typebox.Object({ + d: typebox.Boolean(), + e: typebox.Integer(), + f: typebox.Array(typebox.Object({ g: typebox.Unknown() })), + }), + ), + h: typebox.Optional(typebox.Record(typebox.String(), typebox.String())), + }), + i: typebox.Optional(typebox.BigInt()), + }), + j: typebox.Optional( + typebox.Object({ + k: typebox.Array( + typebox.Record( + typebox.String(), + typebox.Object({ l: typebox.String() }), + ), + ), + }), + ), + }), +).types + ([14320, "instantiations"]) + +bench("arktype: deep object (no baseline)", () => + arktype({ + a: { + b: { + "c?": { + d: "boolean", + e: "number.integer", + f: arktype({ + g: "unknown", + }).array(), + }, + "h?": "Record", + }, + "i?": "bigint", + }, + "j?": { + k: arktype.Record("string", { l: "string" }).array(), + }, + }), +).types + ([16235, "instantiations"]) + +bench("valibot: deep object (no baseline)", () => + valibot.object({ + a: valibot.object({ + b: valibot.object({ + c: valibot.optional( + valibot.object({ + d: valibot.boolean(), + e: valibot.pipe(valibot.number(), valibot.integer()), + f: valibot.array( + valibot.object({ + g: valibot.unknown(), + }), + ), + }), + ), + h: valibot.optional(valibot.record(valibot.string(), valibot.string())), + }), + i: valibot.optional(valibot.bigint()), + }), + j: valibot.optional( + valibot.object({ + k: valibot.array( + valibot.record( + valibot.string(), + valibot.object({ l: valibot.string() }), + ), + ), + }), + ), + }), +).types + ([40168, "instantiations"]) + +bench("zod@3: deep object (no baseline)", () => + zod3.object({ + a: zod3.object({ + b: zod3.object({ + c: zod3.optional( + zod3.object({ + d: zod3.boolean(), + e: zod3.number().int(), + f: zod3.array(zod3.object({ g: zod3.unknown() })), + }), + ), + h: zod3.optional(zod3.record(zod3.string(), zod3.string())), + }), + i: zod3.optional(zod3.bigint()), + }), + j: zod3.optional( + zod3.object({ + k: zod3.array( + zod3.record(zod3.string(), zod3.object({ l: zod3.string() })), + ), + }), + ), + }), +).types + ([40197, "instantiations"]) diff --git a/packages/schema-core/test/types/deep-object.bench.types.ts b/packages/schema-core/test/types/deep-object.bench.types.ts index ceb9f5b7..7746ac4b 100644 --- a/packages/schema-core/test/types/deep-object.bench.types.ts +++ b/packages/schema-core/test/types/deep-object.bench.types.ts @@ -1,44 +1,44 @@ -import { bench } from '@ark/attest' -import { t as core } from '@traversable/schema-core' -import { z as zod3 } from 'zod3' -import { z as zod4 } from 'zod4' -import { type as arktype } from 'arktype' -import { Type as typebox } from '@sinclair/typebox' -import * as valibot from 'valibot' - -export declare let RESULTS: [ - { - libraryName: "@traversable/schema" - instantiations: 1149 - }, - { - libraryName: "zod@4" - instantiations: 3199 - }, - { - libraryName: "arktype" - instantiations: 12359 - }, - { - libraryName: "@sinclair/typebox" - instantiations: 14294 - }, - { - libraryName: "zod@3" - instantiations: 19292 - }, - { - libraryName: "valibot" - instantiations: 40067 - } -] - +import { bench } from "@ark/attest" +import { t as core } from "@traversable/schema-core" +import { z as zod3 } from "zod3" +import { z as zod4 } from "zod4" +import { type as arktype } from "arktype" +import { Type as typebox } from "@sinclair/typebox" +import * as valibot from "valibot" + +export declare let RESULTS: [ + { + libraryName: "@traversable/schema" + instantiations: 1149 + }, + { + libraryName: "zod@4" + instantiations: 3199 + }, + { + libraryName: "arktype" + instantiations: 12359 + }, + { + libraryName: "@sinclair/typebox" + instantiations: 14294 + }, + { + libraryName: "zod@3" + instantiations: 19292 + }, + { + libraryName: "valibot" + instantiations: 40067 + } +] + bench.baseline(() => { core.tuple(core.string) zod3.tuple([zod3.string()]) typebox.Tuple([typebox.String()]) valibot.tuple([valibot.string()]) - arktype(['string']) + arktype(["string"]) }) bench("@traversable/schema: deep object", () => @@ -61,11 +61,13 @@ bench("@traversable/schema: deep object", () => i: core.optional(core.bigint), }), j: core.optional( - core.object({ k: core.array(core.record(core.object({ l: core.string }))) }), + core.object({ + k: core.array(core.record(core.object({ l: core.string }))), + }), ), - }) + }), ).types - ([1149,"instantiations"]) + ([1149, "instantiations"]) bench("zod@4: deep object", () => zod4.object({ @@ -84,12 +86,14 @@ bench("zod@4: deep object", () => }), j: zod4.optional( zod4.object({ - k: zod4.array(zod4.record(zod4.string(), zod4.object({ l: zod4.string() }))), + k: zod4.array( + zod4.record(zod4.string(), zod4.object({ l: zod4.string() })), + ), }), ), - }) + }), ).types - ([3199,"instantiations"]) + ([3199, "instantiations"]) bench("arktype: deep object", () => arktype({ @@ -109,9 +113,9 @@ bench("arktype: deep object", () => "j?": { k: arktype.Record("string", { l: "string" }).array(), }, - }) + }), ).types - ([12359,"instantiations"]) + ([12359, "instantiations"]) bench("@sinclair/typebox: deep object", () => typebox.Object({ @@ -131,36 +135,42 @@ bench("@sinclair/typebox: deep object", () => j: typebox.Optional( typebox.Object({ k: typebox.Array( - typebox.Record(typebox.String(), typebox.Object({ l: typebox.String() })), + typebox.Record( + typebox.String(), + typebox.Object({ l: typebox.String() }), + ), ), }), ), - }) + }), ).types - ([14294,"instantiations"]) + ([14294, "instantiations"]) -bench("zod@3: deep object", () => zod3.object({ - a: zod3.object({ - b: zod3.object({ - c: zod3.optional( - zod3.object({ - d: zod3.boolean(), - e: zod3.number().int(), - f: zod3.array(zod3.object({ g: zod3.unknown() })), - }), - ), - h: zod3.optional(zod3.record(zod3.string(), zod3.string())), +bench("zod@3: deep object", () => + zod3.object({ + a: zod3.object({ + b: zod3.object({ + c: zod3.optional( + zod3.object({ + d: zod3.boolean(), + e: zod3.number().int(), + f: zod3.array(zod3.object({ g: zod3.unknown() })), + }), + ), + h: zod3.optional(zod3.record(zod3.string(), zod3.string())), + }), + i: zod3.optional(zod3.bigint()), }), - i: zod3.optional(zod3.bigint()), + j: zod3.optional( + zod3.object({ + k: zod3.array( + zod3.record(zod3.string(), zod3.object({ l: zod3.string() })), + ), + }), + ), }), - j: zod3.optional( - zod3.object({ - k: zod3.array(zod3.record(zod3.string(), zod3.object({ l: zod3.string() }))), - }), - ), -}) ).types - ([19292,"instantiations"]) + ([19292, "instantiations"]) bench("valibot: deep object", () => valibot.object({ @@ -183,9 +193,14 @@ bench("valibot: deep object", () => }), j: valibot.optional( valibot.object({ - k: valibot.array(valibot.record(valibot.string(), valibot.object({ l: valibot.string() }))), + k: valibot.array( + valibot.record( + valibot.string(), + valibot.object({ l: valibot.string() }), + ), + ), }), ), - }) + }), ).types - ([40067,"instantiations"]) + ([40067, "instantiations"]) diff --git a/packages/schema-core/test/types/object--no-baseline.bench.types.ts b/packages/schema-core/test/types/object--no-baseline.bench.types.ts new file mode 100644 index 00000000..a98392ad --- /dev/null +++ b/packages/schema-core/test/types/object--no-baseline.bench.types.ts @@ -0,0 +1,84 @@ +import { bench } from "@ark/attest" +import { t as core } from "@traversable/schema-core" +import { z as zod3 } from "zod3" +import { z as zod4 } from "zod4" +import { type as arktype } from "arktype" +import { Type as typebox } from "@sinclair/typebox" +import * as valibot from "valibot" + +export declare let RESULTS: [ + { + libraryName: "@traversable/schema" + instantiations: 102 + }, + { + libraryName: "@sinclair/typebox" + instantiations: 159 + }, + { + libraryName: "zod@4" + instantiations: 484 + }, + { + libraryName: "arktype" + instantiations: 5011 + }, + { + libraryName: "valibot" + instantiations: 15973 + }, + { + libraryName: "zod@3" + instantiations: 25146 + } +] + +bench.baseline(() => void {}) + +bench("@traversable/schema: object (no baseline)", () => + core.object({ + a: core.boolean, + b: core.optional(core.number), + }), +).types + ([102, "instantiations"]) + +bench("zod@4: object", () => + zod4.object({ + a: zod4.boolean(), + b: zod4.optional(zod4.number()), + }), +).types + ([484, "instantiations"]) + +bench("arktype: object (no baseline)", () => + arktype({ + a: "boolean", + "b?": "number", + }), +).types + ([5011, "instantiations"]) + +bench("@sinclair/typebox: object (no baseline)", () => + typebox.Object({ + a: typebox.Boolean(), + b: typebox.Optional(typebox.Number()), + }), +).types + ([159, "instantiations"]) + +bench("zod@3: object", () => + zod3.object({ + a: zod3.boolean(), + b: zod3.optional(zod3.number()), + }), +).types + ([25146, "instantiations"]) + +bench("valibot: object (no baseline)", () => + valibot.object({ + a: valibot.boolean(), + b: valibot.optional(valibot.number()), + }), +).types + ([15973, "instantiations"]) diff --git a/packages/schema-core/test/types/object.bench.types.ts b/packages/schema-core/test/types/object.bench.types.ts new file mode 100644 index 00000000..97aad00c --- /dev/null +++ b/packages/schema-core/test/types/object.bench.types.ts @@ -0,0 +1,90 @@ +import { bench } from "@ark/attest" +import { t as core } from "@traversable/schema-core" +import { z as zod3 } from "zod3" +import { z as zod4 } from "zod4" +import { type as arktype } from "arktype" +import { Type as typebox } from "@sinclair/typebox" +import * as valibot from "valibot" + +export declare let RESULTS: [ + { + libraryName: "@traversable/schema" + instantiations: 86 + }, + { + libraryName: "@sinclair/typebox" + instantiations: 150 + }, + { + libraryName: "zod@4" + instantiations: 356 + }, + { + libraryName: "arktype" + instantiations: 1663 + }, + { + libraryName: "zod@3" + instantiations: 4541 + }, + { + libraryName: "valibot" + instantiations: 16091 + } +] + +bench.baseline(() => { + core.tuple(core.string) + zod3.tuple([zod3.string()]) + typebox.Tuple([typebox.String()]) + valibot.tuple([valibot.string()]) + arktype(["string"]) +}) + +bench("@traversable/schema: object", () => + core.object({ + a: core.boolean, + b: core.optional(core.number), + }), +).types + ([86, "instantiations"]) + +bench("zod@4: object", () => + zod4.object({ + a: zod4.boolean(), + b: zod4.optional(zod4.number()), + }), +).types + ([356, "instantiations"]) + +bench("arktype: object", () => + arktype({ + a: "boolean", + "b?": "number", + }), +).types + ([1663, "instantiations"]) + +bench("@sinclair/typebox: object", () => + typebox.Object({ + a: typebox.Boolean(), + b: typebox.Optional(typebox.Number()), + }), +).types + ([150, "instantiations"]) + +bench("zod@3: object", () => + zod3.object({ + a: zod3.boolean(), + b: zod3.optional(zod3.number()), + }), +).types + ([4541, "instantiations"]) + +bench("valibot: object", () => + valibot.object({ + a: valibot.boolean(), + b: valibot.optional(valibot.number()), + }), +).types + ([16091, "instantiations"]) diff --git a/packages/schema-core/tsconfig.bench.json b/packages/schema-core/tsconfig.bench.json index fc2025ce..5f6952d1 100644 --- a/packages/schema-core/tsconfig.bench.json +++ b/packages/schema-core/tsconfig.bench.json @@ -16,5 +16,5 @@ "references": [ { "path": "tsconfig.src.json" } ], - "include": ["bench"] + "include": ["bench", "test/types/deep-object--no-baseline.bench.types.ts", "test/types/deep-object.bench.types.ts", "test/types/object--no-baseline.bench.types.ts", "test/types/object.bench.types.ts"] } diff --git a/packages/schema-core/tsconfig.json b/packages/schema-core/tsconfig.json index f4bb1bdf..1392c4ec 100644 --- a/packages/schema-core/tsconfig.json +++ b/packages/schema-core/tsconfig.json @@ -1,12 +1,38 @@ { - "extends": "../../tsconfig.base.json", - "include": [], + "compilerOptions": { + "esModuleInterop": true, + "strict": true, + "tsBuildInfoFile": ".tsbuildinfo/bench.tsbuildinfo", + "rootDir": "bench", + "types": [ + "node" + ], + "noEmit": true, + "paths": { + "@traversable/registry": [ + "../../packages/registry/src/index.js" + ], + "@traversable/registry/*": [ + "../../packages/registry/src/*.js" + ], + "@traversable/schema-core": [ + "../../packages/schema-core/src/index.js" + ], + "@traversable/schema-core/*": [ + "../../packages/schema-core/src/*.js" + ] + } + }, "references": [ { "path": "tsconfig.src.json" - }, - { - "path": "tsconfig.test.json" } + ], + "include": [ + "bench", + "test/types/deep-object--no-baseline.bench.types.ts", + "test/types/deep-object.bench.types.ts", + "test/types/object--no-baseline.bench.types.ts", + "test/types/object.bench.types.ts" ] } \ No newline at end of file From 265ef055464ae1e0bd62b056ea0e8021d284a1e1 Mon Sep 17 00:00:00 2001 From: Andrew Jarrett Date: Thu, 24 Apr 2025 06:00:16 -0500 Subject: [PATCH 45/45] fix(schema): fixes build --- packages/schema-core/src/namespace.ts | 1 - packages/schema-core/src/types.ts | 57 +------------------- packages/schema-core/tsconfig.json | 40 ++------------ packages/schema-generator/src/generate.ts | 15 ++++-- packages/schema/src/__schemas__/any.ts | 2 +- packages/schema/src/__schemas__/array.ts | 2 +- packages/schema/src/__schemas__/bigint.ts | 2 +- packages/schema/src/__schemas__/boolean.ts | 2 +- packages/schema/src/__schemas__/eq.ts | 2 +- packages/schema/src/__schemas__/integer.ts | 2 +- packages/schema/src/__schemas__/intersect.ts | 2 +- packages/schema/src/__schemas__/never.ts | 2 +- packages/schema/src/__schemas__/null.ts | 2 +- packages/schema/src/__schemas__/number.ts | 2 +- packages/schema/src/__schemas__/object.ts | 4 +- packages/schema/src/__schemas__/of.ts | 2 +- packages/schema/src/__schemas__/optional.ts | 2 +- packages/schema/src/__schemas__/record.ts | 2 +- packages/schema/src/__schemas__/string.ts | 2 +- packages/schema/src/__schemas__/symbol.ts | 2 +- packages/schema/src/__schemas__/tuple.ts | 2 +- packages/schema/src/__schemas__/undefined.ts | 2 +- packages/schema/src/__schemas__/union.ts | 2 +- packages/schema/src/__schemas__/unknown.ts | 2 +- packages/schema/src/__schemas__/void.ts | 2 +- pnpm-lock.yaml | 4 +- 26 files changed, 43 insertions(+), 118 deletions(-) diff --git a/packages/schema-core/src/namespace.ts b/packages/schema-core/src/namespace.ts index 15667d0e..ba3b5e74 100644 --- a/packages/schema-core/src/namespace.ts +++ b/packages/schema-core/src/namespace.ts @@ -71,7 +71,6 @@ export type { SchemaLike, Typeguard, UnknownSchema, - Unspecified, ValidateTuple, } from './types.js' diff --git a/packages/schema-core/src/types.ts b/packages/schema-core/src/types.ts index fd0c8821..3551b13f 100644 --- a/packages/schema-core/src/types.ts +++ b/packages/schema-core/src/types.ts @@ -28,10 +28,8 @@ export type { TypePredicate_ as TypePredicate } type TypePredicate_ = never | TypePredicate<[I, O]> interface TypePredicate { (u: T[0]): u is T[1] - // (u: T[1]): boolean } -export interface Unspecified extends LowerBound { } export interface LowerBound { (u: unknown): u is any tag: string @@ -46,19 +44,13 @@ export interface UnknownSchema { _type: unknown } -export interface Schema { +export interface Schema { tag?: any def?: Fn['def'] _type?: Fn['_type'] (u: unknown): u is this['_type'] } -// extends TypePredicate<[Source, Fn['_type']]> { -// tag?: Fn['tag'] -// def?: Fn['def'] -// _type?: Fn['_type'] -// } - export interface Predicate { (value: T): boolean (value?: T): boolean @@ -80,19 +72,7 @@ export type ValidateOptionals : ['ok'] : ['ok'] - ; - -// export type ValidateOptionals, Acc extends unknown[] = []> -// = LowerBound extends S[number] -// ? S extends [infer H, ...infer T] -// ? LowerBound extends Partial -// ? T[number] extends LowerBound -// ? ['ok'] -// : [...Acc, H, ...{ [Ix in keyof T]: T[Ix] extends LowerBound ? T[Ix] : InvalidItem }] -// : ValidateOptionals -// : ['ok'] -// : ['ok'] -// ; + export type Optional = never | string extends K ? string @@ -111,17 +91,6 @@ export type IntersectType ? IntersectType : Out -// export type TupleType = never -// | { [symbol.optional]: number } extends Partial -// ? T extends readonly [infer Head, ...infer Tail] -// ? [Head] extends [{ [symbol.optional]: number }] ? Label< -// { [ix in keyof Out]: Out[ix]['_type' & keyof Out[ix]] }, -// { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } -// > -// : TupleType -// : never -// : { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } - export type TupleType> = [Opt] extends [never] ? { [ix in keyof S]: S[ix]['_type' & keyof S[ix]] } : S extends readonly [infer Head, ...infer Tail] @@ -132,34 +101,12 @@ export type TupleType : { [ix in keyof S]: S[ix]['_type' & keyof S[ix]] } -// [Self['opt' & keyof Self]] extends [never] ? { [ix in keyof S]: S[ix]['_type' & keyof S[ix]] } - -// never - -// | { tag: URI.optional } extends Partial -// ? -// T extends readonly [infer Head, ...infer Tail] -// ? { tag: URI.optional } extends Partial ? Label< -// { [ix in keyof Out]: Out[ix]['_type' & keyof Out[ix]] }, -// { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } -// > -// : TupleType -// : never -// : { [ix in keyof T]: T[ix]['_type' & keyof T[ix]] } - export type FirstOptionalItem = S extends readonly [infer H, ...infer T] ? symbol.optional extends keyof H ? Offset['length'] - // ? { [symbol.optional]?: number } extends Partial ? Offset['length'] : FirstOptionalItem : never -// export type FirstOptionalItem -// = S extends readonly [infer H, ...infer T] -// ? { [symbol.optional]: number } extends Partial ? Offset['length'] -// : FirstOptionalItem -// : never - export type BoolLookup = never | { true: top false: bottom diff --git a/packages/schema-core/tsconfig.json b/packages/schema-core/tsconfig.json index 1392c4ec..2c291d21 100644 --- a/packages/schema-core/tsconfig.json +++ b/packages/schema-core/tsconfig.json @@ -1,38 +1,8 @@ { - "compilerOptions": { - "esModuleInterop": true, - "strict": true, - "tsBuildInfoFile": ".tsbuildinfo/bench.tsbuildinfo", - "rootDir": "bench", - "types": [ - "node" - ], - "noEmit": true, - "paths": { - "@traversable/registry": [ - "../../packages/registry/src/index.js" - ], - "@traversable/registry/*": [ - "../../packages/registry/src/*.js" - ], - "@traversable/schema-core": [ - "../../packages/schema-core/src/index.js" - ], - "@traversable/schema-core/*": [ - "../../packages/schema-core/src/*.js" - ] - } - }, + "extends": "../../tsconfig.base.json", + "include": [], "references": [ - { - "path": "tsconfig.src.json" - } - ], - "include": [ - "bench", - "test/types/deep-object--no-baseline.bench.types.ts", - "test/types/deep-object.bench.types.ts", - "test/types/object--no-baseline.bench.types.ts", - "test/types/object.bench.types.ts" + { "path": "tsconfig.src.json" }, + { "path": "tsconfig.test.json" } ] -} \ No newline at end of file +} diff --git a/packages/schema-generator/src/generate.ts b/packages/schema-generator/src/generate.ts index b881eab7..94c4d397 100644 --- a/packages/schema-generator/src/generate.ts +++ b/packages/schema-generator/src/generate.ts @@ -96,9 +96,18 @@ export function generateSchemas( }) } -type WriteSchemasArgs = [sources: T, targets: Record, pkgNameForHeader: string] -export function writeSchemas>>(sources: WriteSchemasArgs[0], targets: WriteSchemasArgs[1], pkgNameForHeader: WriteSchemasArgs[2]): void -export function writeSchemas(...args: WriteSchemasArgs>>): void { +export function writeSchemas>>( + sources: T, + targets: Record, + pkgNameForHeader: string +): void +export function writeSchemas( + ...args: [ + sources: Record>, + targets: Record, + pkgNameForHeader: string + ] +): void { let schemas = generateSchemas(...args) for (let [target, content] of schemas) { void fs.writeFileSync(target, content) diff --git a/packages/schema/src/__schemas__/any.ts b/packages/schema/src/__schemas__/any.ts index 701e54f2..88f53e49 100644 --- a/packages/schema/src/__schemas__/any.ts +++ b/packages/schema/src/__schemas__/any.ts @@ -4,7 +4,7 @@ */ import type { Equal, Unknown } from '@traversable/registry' import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { ValidationFn } from '@traversable/derive-validators' //////////////////// /// equals /// diff --git a/packages/schema/src/__schemas__/array.ts b/packages/schema/src/__schemas__/array.ts index 7f149e0d..745032f0 100644 --- a/packages/schema/src/__schemas__/array.ts +++ b/packages/schema/src/__schemas__/array.ts @@ -26,7 +26,7 @@ import { } from '@traversable/registry' import type { Guarded, Schema, SchemaLike } from '../_namespace.js' import type { of } from './of.js' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { SizeBounds } from '@traversable/schema-to-json-schema' import { hasSchema } from '@traversable/schema-to-json-schema' import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/bigint.ts b/packages/schema/src/__schemas__/bigint.ts index 6c0e5940..39609a10 100644 --- a/packages/schema/src/__schemas__/bigint.ts +++ b/packages/schema/src/__schemas__/bigint.ts @@ -13,7 +13,7 @@ import { } from '@traversable/registry' import type { ValidationError, ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' -import type { t } from '../index.js' +import type { t } from '../_exports.js' //////////////////// /// equals /// export type equals = Equal diff --git a/packages/schema/src/__schemas__/boolean.ts b/packages/schema/src/__schemas__/boolean.ts index 20af67b0..b34a306a 100644 --- a/packages/schema/src/__schemas__/boolean.ts +++ b/packages/schema/src/__schemas__/boolean.ts @@ -4,7 +4,7 @@ */ import type { Equal, Unknown } from '@traversable/registry' import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' //////////////////// /// equals /// diff --git a/packages/schema/src/__schemas__/eq.ts b/packages/schema/src/__schemas__/eq.ts index 655eeac1..2508f290 100644 --- a/packages/schema/src/__schemas__/eq.ts +++ b/packages/schema/src/__schemas__/eq.ts @@ -19,7 +19,7 @@ import { Object_assign, URI } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import { stringify } from '@traversable/schema-to-string' import type { Validate } from '@traversable/derive-validators' import { Errors } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/integer.ts b/packages/schema/src/__schemas__/integer.ts index 5e31c890..dfa8344b 100644 --- a/packages/schema/src/__schemas__/integer.ts +++ b/packages/schema/src/__schemas__/integer.ts @@ -21,7 +21,7 @@ import { URI, within } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { NumericBounds } from '@traversable/schema-to-json-schema' import { getNumericBounds } from '@traversable/schema-to-json-schema' import type { ValidationError, ValidationFn } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/intersect.ts b/packages/schema/src/__schemas__/intersect.ts index 2fb8be06..8170fbbf 100644 --- a/packages/schema/src/__schemas__/intersect.ts +++ b/packages/schema/src/__schemas__/intersect.ts @@ -24,7 +24,7 @@ import type { Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import { getSchema } from '@traversable/schema-to-json-schema' import { callToString } from '@traversable/schema-to-string' import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/never.ts b/packages/schema/src/__schemas__/never.ts index ef051681..57f7ab19 100644 --- a/packages/schema/src/__schemas__/never.ts +++ b/packages/schema/src/__schemas__/never.ts @@ -4,7 +4,7 @@ */ import type { Equal, Unknown } from '@traversable/registry' import { Object_assign, URI } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import { NullaryErrors, type ValidationFn } from '@traversable/derive-validators' //////////////////// /// equals /// diff --git a/packages/schema/src/__schemas__/null.ts b/packages/schema/src/__schemas__/null.ts index 4a7d423f..e0e468d1 100644 --- a/packages/schema/src/__schemas__/null.ts +++ b/packages/schema/src/__schemas__/null.ts @@ -4,7 +4,7 @@ */ import type { Equal, Unknown } from '@traversable/registry' import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' //////////////////// diff --git a/packages/schema/src/__schemas__/number.ts b/packages/schema/src/__schemas__/number.ts index 9708d5b0..55db83f8 100644 --- a/packages/schema/src/__schemas__/number.ts +++ b/packages/schema/src/__schemas__/number.ts @@ -19,7 +19,7 @@ import { URI, within } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { NumericBounds } from '@traversable/schema-to-json-schema' import { getNumericBounds } from '@traversable/schema-to-json-schema' import type { ValidationError, ValidationFn } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/object.ts b/packages/schema/src/__schemas__/object.ts index f71da03a..ef38f97c 100644 --- a/packages/schema/src/__schemas__/object.ts +++ b/packages/schema/src/__schemas__/object.ts @@ -36,8 +36,8 @@ import type { Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../index.js' -import { getConfig } from '../index.js' +import type { t } from '../_exports.js' +import { getConfig } from '../_exports.js' import type { RequiredKeys } from '@traversable/schema-to-json-schema' import { isRequired, property } from '@traversable/schema-to-json-schema' import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/of.ts b/packages/schema/src/__schemas__/of.ts index 11d79efd..bda3e284 100644 --- a/packages/schema/src/__schemas__/of.ts +++ b/packages/schema/src/__schemas__/of.ts @@ -10,7 +10,7 @@ import type { Guarded, SchemaLike } from '../_namespace.js' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' //////////////////// diff --git a/packages/schema/src/__schemas__/optional.ts b/packages/schema/src/__schemas__/optional.ts index d471cfab..1e57374a 100644 --- a/packages/schema/src/__schemas__/optional.ts +++ b/packages/schema/src/__schemas__/optional.ts @@ -20,7 +20,7 @@ import { URI } from '@traversable/registry' import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import { getSchema, wrapOptional } from '@traversable/schema-to-json-schema' import { callToString } from '@traversable/schema-to-string' import type { Validate, ValidationFn, Validator } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/record.ts b/packages/schema/src/__schemas__/record.ts index 4daba2ea..7e8996cf 100644 --- a/packages/schema/src/__schemas__/record.ts +++ b/packages/schema/src/__schemas__/record.ts @@ -17,7 +17,7 @@ import { URI } from '@traversable/registry' import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import { getSchema } from '@traversable/schema-to-json-schema' import { callToString } from '@traversable/schema-to-string' import type { ValidationError, ValidationFn, Validator } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/string.ts b/packages/schema/src/__schemas__/string.ts index 9308dec2..28b18d12 100644 --- a/packages/schema/src/__schemas__/string.ts +++ b/packages/schema/src/__schemas__/string.ts @@ -20,7 +20,7 @@ import { URI, within } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { SizeBounds } from '@traversable/schema-to-json-schema' import type { ValidationError, ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/symbol.ts b/packages/schema/src/__schemas__/symbol.ts index 04bb7b06..605e3dc4 100644 --- a/packages/schema/src/__schemas__/symbol.ts +++ b/packages/schema/src/__schemas__/symbol.ts @@ -4,7 +4,7 @@ */ import type { Equal, Unknown } from '@traversable/registry' import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' //////////////////// diff --git a/packages/schema/src/__schemas__/tuple.ts b/packages/schema/src/__schemas__/tuple.ts index ae6f6413..4b95fcff 100644 --- a/packages/schema/src/__schemas__/tuple.ts +++ b/packages/schema/src/__schemas__/tuple.ts @@ -34,7 +34,7 @@ import type { ValidateTuple } from '../_namespace.js' import type { optional } from './optional.js' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { MinItems } from '@traversable/schema-to-json-schema' import { applyTupleOptionality, minItems } from '@traversable/schema-to-json-schema' import { hasToString } from '@traversable/schema-to-string' diff --git a/packages/schema/src/__schemas__/undefined.ts b/packages/schema/src/__schemas__/undefined.ts index 4f16e4af..9eaf8412 100644 --- a/packages/schema/src/__schemas__/undefined.ts +++ b/packages/schema/src/__schemas__/undefined.ts @@ -4,7 +4,7 @@ */ import type { Equal, Unknown } from '@traversable/registry' import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' //////////////////// diff --git a/packages/schema/src/__schemas__/union.ts b/packages/schema/src/__schemas__/union.ts index f7247ec6..a69abce7 100644 --- a/packages/schema/src/__schemas__/union.ts +++ b/packages/schema/src/__schemas__/union.ts @@ -19,7 +19,7 @@ import { URI } from '@traversable/registry' import type { Entry, Schema, SchemaLike } from '../_namespace.js' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import { getSchema } from '@traversable/schema-to-json-schema' import { callToString } from '@traversable/schema-to-string' import type { Validate, ValidationError, Validator } from '@traversable/derive-validators' diff --git a/packages/schema/src/__schemas__/unknown.ts b/packages/schema/src/__schemas__/unknown.ts index da8224ea..852327b3 100644 --- a/packages/schema/src/__schemas__/unknown.ts +++ b/packages/schema/src/__schemas__/unknown.ts @@ -4,7 +4,7 @@ */ import type { Equal, Unknown } from '@traversable/registry' import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { ValidationFn } from '@traversable/derive-validators' //////////////////// /// equals /// diff --git a/packages/schema/src/__schemas__/void.ts b/packages/schema/src/__schemas__/void.ts index c498fecc..4043f69b 100644 --- a/packages/schema/src/__schemas__/void.ts +++ b/packages/schema/src/__schemas__/void.ts @@ -4,7 +4,7 @@ */ import type { Equal, Unknown } from '@traversable/registry' import { Object_assign, Object_is, URI } from '@traversable/registry' -import type { t } from '../index.js' +import type { t } from '../_exports.js' import type { ValidationFn } from '@traversable/derive-validators' import { NullaryErrors } from '@traversable/derive-validators' //////////////////// diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index caba3128..df532548 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -168,9 +168,9 @@ importers: '@traversable/registry': specifier: workspace:^ version: link:../../packages/registry/dist - '@traversable/schema': + '@traversable/schema-core': specifier: workspace:^ - version: link:../../packages/schema/dist + version: link:../../packages/schema-core/dist '@traversable/schema-seed': specifier: workspace:^ version: link:../../packages/schema-seed/dist