nalloc 0.2.2 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +326 -178
  2. package/build/codemod-cli.cjs +153 -0
  3. package/build/codemod-cli.cjs.map +1 -0
  4. package/build/codemod-cli.d.ts +2 -0
  5. package/build/codemod-cli.js +103 -0
  6. package/build/codemod-cli.js.map +1 -0
  7. package/build/codemod.cjs +652 -0
  8. package/build/codemod.cjs.map +1 -0
  9. package/build/codemod.d.ts +29 -0
  10. package/build/codemod.js +634 -0
  11. package/build/codemod.js.map +1 -0
  12. package/build/eslint.cjs +221 -0
  13. package/build/eslint.cjs.map +1 -0
  14. package/build/eslint.d.ts +36 -0
  15. package/build/eslint.js +198 -0
  16. package/build/eslint.js.map +1 -0
  17. package/build/http.cjs +31 -0
  18. package/build/http.cjs.map +1 -0
  19. package/build/http.d.ts +31 -0
  20. package/build/http.js +13 -0
  21. package/build/http.js.map +1 -0
  22. package/build/nonempty.cjs +35 -0
  23. package/build/nonempty.cjs.map +1 -0
  24. package/build/nonempty.d.ts +34 -0
  25. package/build/nonempty.js +14 -0
  26. package/build/nonempty.js.map +1 -0
  27. package/build/option.cjs +1 -1
  28. package/build/option.cjs.map +1 -1
  29. package/build/option.d.ts +2 -2
  30. package/build/option.js +1 -1
  31. package/build/option.js.map +1 -1
  32. package/build/result.cjs +10 -18
  33. package/build/result.cjs.map +1 -1
  34. package/build/result.d.ts +3 -34
  35. package/build/result.js +10 -15
  36. package/build/result.js.map +1 -1
  37. package/build/safe.cjs +8 -0
  38. package/build/safe.cjs.map +1 -1
  39. package/build/safe.d.ts +3 -0
  40. package/build/safe.js +2 -0
  41. package/build/safe.js.map +1 -1
  42. package/build/schema.cjs +32 -0
  43. package/build/schema.cjs.map +1 -0
  44. package/build/schema.d.ts +44 -0
  45. package/build/schema.js +14 -0
  46. package/build/schema.js.map +1 -0
  47. package/package.json +63 -10
  48. package/src/__tests__/codemod.ts +211 -0
  49. package/src/__tests__/eslint.ts +99 -0
  50. package/src/__tests__/fixtures/tsconfig.json +10 -0
  51. package/src/__tests__/http.ts +64 -0
  52. package/src/__tests__/iter.ts +18 -0
  53. package/src/__tests__/nonempty.ts +46 -0
  54. package/src/__tests__/nonempty.types.ts +38 -0
  55. package/src/__tests__/option.ts +4 -0
  56. package/src/__tests__/result.ts +104 -129
  57. package/src/__tests__/result.types.ts +2 -2
  58. package/src/__tests__/schema.ts +58 -0
  59. package/src/codemod-cli.ts +108 -0
  60. package/src/codemod.ts +623 -0
  61. package/src/eslint.ts +145 -0
  62. package/src/http.ts +42 -0
  63. package/src/nonempty.ts +48 -0
  64. package/src/option.ts +3 -4
  65. package/src/result.ts +18 -49
  66. package/src/safe.ts +3 -0
  67. package/src/schema.ts +52 -0
package/src/eslint.ts ADDED
@@ -0,0 +1,145 @@
1
+ import { ESLintUtils, type TSESTree } from '@typescript-eslint/utils';
2
+ import type * as ts from 'typescript';
3
+
4
+ interface RuleDocs {
5
+ description: string;
6
+ requiresTypeChecking?: boolean;
7
+ }
8
+
9
+ const createRule = ESLintUtils.RuleCreator<RuleDocs>((name) => `https://github.com/3axap4eHko/nalloc#${name}`);
10
+
11
+ const UNWRAP_NAMES: ReadonlySet<string> = new Set(['unwrap', 'unwrapErr', 'expect', 'expectErr']);
12
+ const DEFAULT_MODULES: readonly string[] = ['nalloc', 'nalloc/safe', 'nalloc/unsafe', 'nalloc/result', 'nalloc/option'];
13
+ const DEFAULT_TYPE_NAMES: readonly string[] = ['Result', 'Ok', 'Err', 'Option', 'Some', 'None'];
14
+
15
+ function nallocTypeName(type: ts.Type, names: ReadonlySet<string>): string | undefined {
16
+ const direct = type.aliasSymbol?.getName();
17
+ if (direct !== undefined && names.has(direct)) {
18
+ return direct;
19
+ }
20
+ if (type.isUnion()) {
21
+ for (const member of type.types) {
22
+ const name = member.aliasSymbol?.getName();
23
+ if (name !== undefined && names.has(name)) {
24
+ return name;
25
+ }
26
+ }
27
+ }
28
+ return undefined;
29
+ }
30
+
31
+ const mustUse = createRule<[{ typeNames?: string[] }], 'mustUse'>({
32
+ name: 'must-use',
33
+ meta: {
34
+ type: 'problem',
35
+ docs: {
36
+ description: 'Require Result and Option values to be handled instead of silently discarded',
37
+ requiresTypeChecking: true,
38
+ },
39
+ messages: {
40
+ mustUse: 'This {{name}} value is discarded. Handle it with match/unwrapOr/isErr/isNone, return it, or assign it.',
41
+ },
42
+ schema: [
43
+ {
44
+ type: 'object',
45
+ properties: { typeNames: { type: 'array', items: { type: 'string' }, uniqueItems: true } },
46
+ additionalProperties: false,
47
+ },
48
+ ],
49
+ defaultOptions: [{}],
50
+ },
51
+ create(context, [options]) {
52
+ const names = new Set(options.typeNames ?? DEFAULT_TYPE_NAMES);
53
+ const services = ESLintUtils.getParserServices(context);
54
+ const check = (node: TSESTree.Expression): void => {
55
+ const name = nallocTypeName(services.getTypeAtLocation(node), names);
56
+ if (name !== undefined) {
57
+ context.report({ node, messageId: 'mustUse', data: { name } });
58
+ }
59
+ };
60
+ return {
61
+ 'ExpressionStatement > CallExpression'(node: TSESTree.CallExpression): void {
62
+ check(node);
63
+ },
64
+ 'ExpressionStatement > AwaitExpression'(node: TSESTree.AwaitExpression): void {
65
+ check(node);
66
+ },
67
+ };
68
+ },
69
+ });
70
+
71
+ const noUnwrap = createRule<[{ modules?: string[] }], 'noUnwrap'>({
72
+ name: 'no-unwrap',
73
+ meta: {
74
+ type: 'suggestion',
75
+ docs: {
76
+ description: 'Disallow unwrap and expect on Result and Option, which throw on failure',
77
+ },
78
+ messages: {
79
+ noUnwrap: '{{name}} throws on failure. Handle the error with match/unwrapOr/isErr, or turn this rule off in test files.',
80
+ },
81
+ schema: [
82
+ {
83
+ type: 'object',
84
+ properties: { modules: { type: 'array', items: { type: 'string' }, uniqueItems: true } },
85
+ additionalProperties: false,
86
+ },
87
+ ],
88
+ defaultOptions: [{}],
89
+ },
90
+ create(context, [options]) {
91
+ const modules = new Set(options.modules ?? DEFAULT_MODULES);
92
+ const namespaceLocals = new Set<string>();
93
+ const directLocals = new Map<string, string>();
94
+ return {
95
+ ImportDeclaration(node: TSESTree.ImportDeclaration): void {
96
+ if (typeof node.source.value !== 'string' || !modules.has(node.source.value)) {
97
+ return;
98
+ }
99
+ for (const spec of node.specifiers) {
100
+ if (spec.type === 'ImportNamespaceSpecifier') {
101
+ namespaceLocals.add(spec.local.name);
102
+ } else if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
103
+ const imported = spec.imported.name;
104
+ if (imported === 'Result' || imported === 'Option') {
105
+ namespaceLocals.add(spec.local.name);
106
+ } else if (UNWRAP_NAMES.has(imported)) {
107
+ directLocals.set(spec.local.name, imported);
108
+ }
109
+ }
110
+ }
111
+ },
112
+ CallExpression(node: TSESTree.CallExpression): void {
113
+ const callee = node.callee;
114
+ if (callee.type === 'MemberExpression' && !callee.computed && callee.property.type === 'Identifier' && callee.object.type === 'Identifier') {
115
+ if (UNWRAP_NAMES.has(callee.property.name) && namespaceLocals.has(callee.object.name)) {
116
+ context.report({ node: callee, messageId: 'noUnwrap', data: { name: callee.property.name } });
117
+ }
118
+ return;
119
+ }
120
+ if (callee.type === 'Identifier') {
121
+ const imported = directLocals.get(callee.name);
122
+ if (imported !== undefined) {
123
+ context.report({ node: callee, messageId: 'noUnwrap', data: { name: imported } });
124
+ }
125
+ }
126
+ },
127
+ };
128
+ },
129
+ });
130
+
131
+ export const rules = { 'must-use': mustUse, 'no-unwrap': noUnwrap };
132
+
133
+ const plugin: { meta: { name: string }; rules: typeof rules; configs: Record<string, unknown> } = {
134
+ meta: { name: 'nalloc' },
135
+ rules,
136
+ configs: {},
137
+ };
138
+
139
+ plugin.configs.recommended = {
140
+ plugins: { nalloc: plugin },
141
+ rules: { 'nalloc/must-use': 'error', 'nalloc/no-unwrap': 'error' },
142
+ };
143
+
144
+ export { mustUse, noUnwrap };
145
+ export default plugin;
package/src/http.ts ADDED
@@ -0,0 +1,42 @@
1
+ import { err as ERR } from './types.js';
2
+ import type { Ok, Result } from './types.js';
3
+
4
+ /**
5
+ * Converts a fetch Response into a Result, treating a non-ok status as an Err.
6
+ * Native fetch only rejects on transport errors, never on 4xx/5xx; this closes that gap.
7
+ * The failed Response itself is the error - read its status, headers, or body from it.
8
+ * @param response - The Response to inspect
9
+ * @returns Ok(response) when response.ok, Err(response) otherwise
10
+ * @example
11
+ * import { Result } from 'nalloc';
12
+ * import { fromResponse } from 'nalloc/http';
13
+ * const res = Result.flatMap(await Result.fromPromise(fetch(url)), fromResponse);
14
+ */
15
+ export function fromResponse(response: Response): Result<Response, Response> {
16
+ return response.ok ? (response as Ok<Response>) : ERR(response);
17
+ }
18
+
19
+ /**
20
+ * Runs fetch and forces every failure mode into the error channel.
21
+ * Ok means the request connected AND returned a 2xx status; a non-2xx Response
22
+ * becomes Err(response), and a transport failure becomes Err with the thrown
23
+ * value (per spec: TypeError on network/CORS errors, DOMException on abort/timeout).
24
+ * The body is never read - it stays available to the caller.
25
+ * @param input - The fetch input (URL or Request)
26
+ * @param init - Optional fetch init
27
+ * @returns Promise of Ok(response) for 2xx, Err otherwise
28
+ * @example
29
+ * import { fromFetch } from 'nalloc/http';
30
+ * const res = await fromFetch(url);
31
+ * // Ok(Response) -> connected and 2xx
32
+ * // Err(Response) -> reached the server, non-2xx
33
+ * // Err(TypeError) -> network/CORS failure
34
+ * // Err(DOMException) -> aborted or timed out
35
+ */
36
+ export async function fromFetch(input: string | URL | Request, init?: RequestInit): Promise<Result<Response, Response | TypeError | DOMException>> {
37
+ try {
38
+ return fromResponse(await fetch(input, init));
39
+ } catch (error) {
40
+ return ERR(error as TypeError | DOMException);
41
+ }
42
+ }
@@ -0,0 +1,48 @@
1
+ import { NONE } from './types.js';
2
+ import type { Option, Some } from './types.js';
3
+
4
+ /** An array proven to contain at least one element. The runtime value is a plain array. */
5
+ export type NonEmptyArray<T> = [T, ...T[]];
6
+
7
+ /** A readonly array proven to contain at least one element. The runtime value is a plain array. */
8
+ export type ReadonlyNonEmptyArray<T> = readonly [T, ...T[]];
9
+
10
+ /**
11
+ * Checks whether an array has at least one element.
12
+ * @param values - The array to check
13
+ * @returns true if the array is non-empty
14
+ * @example
15
+ * isNonEmpty([]) // false
16
+ * isNonEmpty([1]) // true
17
+ */
18
+ export function isNonEmpty<T>(values: readonly T[]): values is ReadonlyNonEmptyArray<T> {
19
+ return values.length > 0;
20
+ }
21
+
22
+ /**
23
+ * Asserts that an array is non-empty, throwing otherwise.
24
+ * @param values - The array to check
25
+ * @param message - Custom error message
26
+ * @throws Error if the array is empty
27
+ * @example
28
+ * assertNonEmpty([1]) // passes
29
+ * assertNonEmpty([]) // throws Error
30
+ */
31
+ export function assertNonEmpty<T>(values: readonly T[], message?: string): asserts values is ReadonlyNonEmptyArray<T> {
32
+ if (values.length === 0) {
33
+ throw new Error(message ?? 'Expected array to be non-empty');
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Converts an array to an Option of a non-empty array. Returns the same array
39
+ * value when non-empty - no allocation or cloning.
40
+ * @param values - The array to convert
41
+ * @returns Some(values) typed as non-empty if length > 0, None otherwise
42
+ * @example
43
+ * fromArray([]) // None
44
+ * fromArray([1, 2]) // Some([1, 2]) with non-empty type
45
+ */
46
+ export function fromArray<T>(values: readonly T[]): Option<ReadonlyNonEmptyArray<T>> {
47
+ return values.length > 0 ? (values as Some<ReadonlyNonEmptyArray<T>>) : NONE;
48
+ }
package/src/option.ts CHANGED
@@ -77,15 +77,14 @@ export function assertSome<T>(opt: Option<T>, message?: string): asserts opt is
77
77
  * runtime to preserve zero-allocation semantics. Use assertSome() if you
78
78
  * need runtime validation that a value is Some.
79
79
  *
80
- * @param _ - The value to assert as Option (not validated at runtime)
80
+ * @param value - The value to assert as Option (not validated at runtime)
81
81
  * @example
82
82
  * const value: number | null = getValue();
83
83
  * satisfiesOption(value); // Compiles, but no runtime check
84
84
  * // value is now typed as Option<number>
85
85
  */
86
- export function satisfiesOption<T>(_: Option<T> | T): asserts _ is Option<T> {
87
- // Compile-time only - no runtime validation to preserve zero-allocation semantics.
88
- }
86
+ // oxlint-disable-next-line no-unused-vars
87
+ export function satisfiesOption<T>(value: Option<T> | T): asserts value is Option<T> {}
89
88
 
90
89
  /**
91
90
  * Maps and filters an iterable, collecting only Some values.
package/src/result.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { err as ERR, isOk, isErr, isSome, isNone, NONE, optionOf, isThenable } from './types.js';
2
2
  import type { Ok, Err, Result, Option, Widen, WidenNever, MaybePromise } from './types.js';
3
+ import type { NonEmptyArray } from './nonempty.js';
3
4
 
4
5
  export type { Ok, Err, Result };
5
6
  export { isOk, isErr };
@@ -105,43 +106,6 @@ export async function fromPromise<T, E = unknown>(promise: Promise<T>, onError?:
105
106
  }
106
107
  }
107
108
 
108
- /** A validation issue from a Standard Schema validator. */
109
- export interface SchemaIssue {
110
- readonly message: string;
111
- readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>;
112
- }
113
-
114
- /** Minimal Standard Schema v1 interface (duck-typed, no external dependency). */
115
- export interface StandardSchema<O = unknown> {
116
- readonly '~standard': {
117
- readonly validate: (value: unknown) => StandardSchemaResult<O> | Promise<StandardSchemaResult<O>>;
118
- };
119
- }
120
-
121
- type StandardSchemaResult<O> = { readonly value: O; readonly issues?: undefined } | { readonly issues: ReadonlyArray<SchemaIssue> };
122
-
123
- function schemaResultToResult<O>(sr: StandardSchemaResult<O>): Result<O, readonly SchemaIssue[]> {
124
- return sr.issues ? ERR(sr.issues) : (sr.value as Ok<O>);
125
- }
126
-
127
- /**
128
- * Validates a value against a Standard Schema and returns a Result.
129
- * Works with any Standard Schema v1 compliant library (Zod, Valibot, ArkType, etc.).
130
- * Returns synchronously if the schema validates synchronously.
131
- * @param schema - A Standard Schema v1 compliant schema
132
- * @param value - The value to validate
133
- * @returns Ok(parsed) if valid, Err(issues) if invalid
134
- * @example
135
- * import { z } from 'zod';
136
- * const result = fromSchema(z.string().email(), input);
137
- * // Result<string, readonly SchemaIssue[]>
138
- */
139
- export function fromSchema<O>(schema: StandardSchema<O>, value: unknown): Result<O, readonly SchemaIssue[]> | Promise<Result<O, readonly SchemaIssue[]>> {
140
- const sr = schema['~standard'].validate(value);
141
- if (isThenable(sr)) return sr.then(schemaResultToResult);
142
- return schemaResultToResult(sr);
143
- }
144
-
145
109
  /**
146
110
  * Executes a function that may return sync or async, preserving sync execution when possible.
147
111
  * @param fn - Function that may return T or Promise<T>
@@ -659,7 +623,7 @@ export function collect<T, E>(results: Result<T, E>[]): Result<T[], E> {
659
623
  * collectAll([ok(1), ok(2)]) // Ok([1, 2])
660
624
  * collectAll([ok(1), err('a'), err('b')]) // Err(['a', 'b'])
661
625
  */
662
- export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], E[]> {
626
+ export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], NonEmptyArray<E>> {
663
627
  const oks: T[] = [];
664
628
  const errs: E[] = [];
665
629
  for (let i = 0; i < results.length; i++) {
@@ -670,7 +634,7 @@ export function collectAll<T, E>(results: Result<T, E>[]): Result<T[], E[]> {
670
634
  errs.push(result.error);
671
635
  }
672
636
  }
673
- return errs.length > 0 ? ERR(errs) : (oks as Ok<T[]>);
637
+ return errs.length > 0 ? ERR(errs as NonEmptyArray<E>) : (oks as Ok<T[]>);
674
638
  }
675
639
 
676
640
  /**
@@ -690,14 +654,14 @@ export function all<T, E>(results: Result<T, E>[]): Result<readonly Widen<T>[],
690
654
  * any([err('a'), ok(1), err('b')]) // Ok(1)
691
655
  * any([err('a'), err('b')]) // Err(['a', 'b'])
692
656
  */
693
- export function any<T, E>(results: Result<T, E>[]): Result<Widen<T>, WidenNever<E>[]> {
657
+ export function any<T, E>(results: NonEmptyArray<Result<T, E>>): Result<Widen<T>, NonEmptyArray<WidenNever<E>>> {
694
658
  const errors: WidenNever<E>[] = [];
695
659
  for (let i = 0; i < results.length; i++) {
696
660
  const result = results[i];
697
661
  if (isOk(result)) return result as Ok<Widen<T>>;
698
662
  errors.push((result as Err<WidenNever<E>>).error);
699
663
  }
700
- return ERR(errors);
664
+ return ERR(errors as NonEmptyArray<WidenNever<E>>);
701
665
  }
702
666
 
703
667
  /**
@@ -711,7 +675,7 @@ export function any<T, E>(results: Result<T, E>[]): Result<Widen<T>, WidenNever<
711
675
  */
712
676
  export function transpose<T, E>(result: Result<Option<T>, E>): Option<Result<T, E>> {
713
677
  if (isErr(result)) {
714
- return ERR(result.error) as Option<Result<T, E>>;
678
+ return result as Option<Result<T, E>>;
715
679
  }
716
680
  const opt = result as Option<T>;
717
681
  return isNone(opt) ? NONE : (opt as unknown as Ok<T> as Option<Result<T, E>>);
@@ -909,11 +873,8 @@ export async function safeTryAsync<T>(fn: () => Promise<T>): Promise<Result<T, u
909
873
  }
910
874
 
911
875
  function* unwrapYield<T, E>(result: Result<T, E>): Generator<Err<E>, T> {
912
- if (isErr(result)) {
913
- yield result;
914
- return undefined as never;
915
- }
916
- return result;
876
+ if (isErr(result)) yield result;
877
+ return result as T;
917
878
  }
918
879
 
919
880
  type Unwrapper = <T, E>(result: Result<T, E>) => Generator<Err<E>, T>;
@@ -934,7 +895,11 @@ type Unwrapper = <T, E>(result: Result<T, E>) => Generator<Err<E>, T>;
934
895
  export function gen<E, T>(fn: ($: Unwrapper) => Generator<Err<E>, T>): Result<T, E> {
935
896
  const iter = fn(unwrapYield);
936
897
  const step = iter.next();
937
- if (!step.done) return step.value;
898
+ if (!step.done) {
899
+ // short-circuit abandons the generator mid-body; resume it so try/finally cleanup runs
900
+ iter.return(undefined as never);
901
+ return step.value;
902
+ }
938
903
  return step.value as Ok<T>;
939
904
  }
940
905
 
@@ -952,6 +917,10 @@ export function gen<E, T>(fn: ($: Unwrapper) => Generator<Err<E>, T>): Result<T,
952
917
  export async function genAsync<E, T>(fn: ($: Unwrapper) => AsyncGenerator<Err<E>, T>): Promise<Result<T, E>> {
953
918
  const iter = fn(unwrapYield);
954
919
  const step = await iter.next();
955
- if (!step.done) return step.value;
920
+ if (!step.done) {
921
+ // short-circuit abandons the generator mid-body; resume it so try/finally cleanup runs
922
+ await iter.return(undefined as never);
923
+ return step.value;
924
+ }
956
925
  return step.value as Ok<T>;
957
926
  }
package/src/safe.ts CHANGED
@@ -16,9 +16,12 @@ export type {
16
16
  MaybePromise,
17
17
  } from './types.js';
18
18
  export { safeTry, safeTryAsync, unwrap, gen, genAsync } from './result.js';
19
+ export type { NonEmptyArray, ReadonlyNonEmptyArray } from './nonempty.js';
19
20
  export * as Option from './option.js';
20
21
  export * as Result from './result.js';
21
22
  export * as Iter from './iter.js';
23
+ export * as NonEmpty from './nonempty.js';
24
+ export * as Schema from './schema.js';
22
25
 
23
26
  /**
24
27
  * Threads a value through a sequence of unary functions, left to right.
package/src/schema.ts ADDED
@@ -0,0 +1,52 @@
1
+ import { err as ERR, isThenable } from './types.js';
2
+ import type { Ok, Result } from './types.js';
3
+
4
+ /** A validation issue from a Standard Schema validator. */
5
+ export interface SchemaIssue {
6
+ readonly message: string;
7
+ readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>;
8
+ }
9
+
10
+ /** Minimal Standard Schema v1 interface (duck-typed, no external dependency). */
11
+ export interface StandardSchema<O = unknown> {
12
+ readonly '~standard': {
13
+ readonly validate: (value: unknown) => StandardSchemaResult<O> | Promise<StandardSchemaResult<O>>;
14
+ };
15
+ }
16
+
17
+ type StandardSchemaResult<O> = { readonly value: O; readonly issues?: undefined } | { readonly issues: ReadonlyArray<SchemaIssue> };
18
+
19
+ function schemaResultToResult<O>(sr: StandardSchemaResult<O>): Result<O, readonly SchemaIssue[]> {
20
+ return sr.issues ? ERR(sr.issues) : (sr.value as Ok<O>);
21
+ }
22
+
23
+ /**
24
+ * Validates a value against a Standard Schema and returns a Result.
25
+ * Works with any Standard Schema v1 compliant library (Zod, Valibot, ArkType, etc.).
26
+ * Returns synchronously if the schema validates synchronously.
27
+ * @param schema - A Standard Schema v1 compliant schema
28
+ * @param value - The value to validate
29
+ * @returns Ok(parsed) if valid, Err(issues) if invalid
30
+ * @example
31
+ * import { z } from 'zod';
32
+ * const result = fromSchema(z.string().email(), input);
33
+ * // Result<string, readonly SchemaIssue[]>
34
+ */
35
+ export function fromSchema<O>(schema: StandardSchema<O>, value: unknown): Result<O, readonly SchemaIssue[]> | Promise<Result<O, readonly SchemaIssue[]>> {
36
+ const sr = schema['~standard'].validate(value);
37
+ if (isThenable(sr)) return sr.then(schemaResultToResult);
38
+ return schemaResultToResult(sr);
39
+ }
40
+
41
+ /**
42
+ * Binds a Standard Schema once and returns a reusable validator function.
43
+ * The partial-application form of fromSchema - validate many values against one schema.
44
+ * @param schema - A Standard Schema v1 compliant schema
45
+ * @returns A function that validates a value and returns a Result
46
+ * @example
47
+ * const parseUser = wrapSchema(userSchema);
48
+ * const result = parseUser(input); // Result<User, readonly SchemaIssue[]>
49
+ */
50
+ export function wrapSchema<O>(schema: StandardSchema<O>): (value: unknown) => Result<O, readonly SchemaIssue[]> | Promise<Result<O, readonly SchemaIssue[]>> {
51
+ return (value) => fromSchema(schema, value);
52
+ }