@loomcli/validators 0.0.0 → 0.6.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.
@@ -0,0 +1,31 @@
1
+ import { boundsIssue, boundsSchema, readBounds, withinBounds, withoutNegativeZero, } from './bounds.js';
2
+ import { integerCodes } from './codes.js';
3
+ import { createValidator } from './create.js';
4
+ import { reject } from './issues.js';
5
+ const digits = /^-?[0-9]+$/u;
6
+ /** The safe integer a token spells in decimal digits, `-0` as `0`, or undefined when none. */
7
+ function readInteger(raw) {
8
+ if (!digits.test(raw)) {
9
+ return undefined;
10
+ }
11
+ const value = Number(raw);
12
+ return Number.isSafeInteger(value) ? withoutNegativeZero(value) : undefined;
13
+ }
14
+ /** A decimal whole number that is a safe integer within `min` and `max`, both inclusive. */
15
+ function integer(options) {
16
+ const bounds = readBounds(options, {
17
+ accepts: Number.isSafeInteger,
18
+ correction: 'Supply a whole number from -9007199254740991 through 9007199254740991.',
19
+ factory: 'integer',
20
+ requirement: 'a safe integer',
21
+ });
22
+ const issue = boundsIssue(integerCodes, bounds);
23
+ return createValidator({
24
+ inputSchema: boundsSchema('integer', bounds),
25
+ parse: (raw) => {
26
+ const value = readInteger(raw);
27
+ return value !== undefined && withinBounds(value, bounds) ? { value } : reject(issue);
28
+ },
29
+ });
30
+ }
31
+ export { integer, readInteger };
@@ -0,0 +1,24 @@
1
+ import type { StandardSchemaV1 } from '@loomcli/core';
2
+ /**
3
+ * One issue code a validator package declares: the code string, the schema its parameters pass,
4
+ * a builder for the issue a `parse` function returns, and a typed read of that issue.
5
+ */
6
+ interface IssueCode<Params> {
7
+ readonly code: string;
8
+ readonly schema: StandardSchemaV1<Params>;
9
+ issue(this: void, params: Params): StandardSchemaV1.Issue;
10
+ read(this: void, issue: StandardSchemaV1.Issue): Params | undefined;
11
+ }
12
+ /** What `issueCode` declares beside the code: the parameter schema and the one sentence. */
13
+ interface IssueCodeConfig<Params> {
14
+ schema: StandardSchemaV1<Params>;
15
+ message: (params: Params) => string;
16
+ }
17
+ /**
18
+ * Declares one issue code with its parameter schema and its one sentence, and returns a frozen
19
+ * descriptor. `issue` builds the rejection a `parse` function returns, and `read` recovers the
20
+ * typed parameters from an issue carrying the code.
21
+ */
22
+ declare function issueCode<Params>(code: string, config: IssueCodeConfig<Params>): IssueCode<Params>;
23
+ export { issueCode };
24
+ export type { IssueCode, IssueCodeConfig };
@@ -0,0 +1,183 @@
1
+ import { isRuleIdentity } from '@loomcli/core';
2
+ import { isPlainObject } from './data.js';
3
+ import { fault, quote } from './faults.js';
4
+ import { issueCodeConfig, issueCodeName, issueCodeSchema, issueParameters } from './rules.js';
5
+ /** The Standard Schema version this package reads. */
6
+ const standardVersion = 1;
7
+ /** Whether a value is an object or a function, which alone can carry fields. */
8
+ function isObjectLike(value) {
9
+ return value !== null && (typeof value === 'object' || typeof value === 'function');
10
+ }
11
+ /**
12
+ * Whether a value answers the Standard Schema v1 contract, as core checks a validator. A value
13
+ * whose fields throw when read, through a getter or a proxy trap, is not a Standard Schema.
14
+ */
15
+ function isStandardSchema(value) {
16
+ try {
17
+ if (!isObjectLike(value)) {
18
+ return false;
19
+ }
20
+ const standard = '~standard' in value ? value['~standard'] : undefined;
21
+ return (standard !== null &&
22
+ typeof standard === 'object' &&
23
+ 'version' in standard &&
24
+ standard.version === standardVersion &&
25
+ 'vendor' in standard &&
26
+ typeof standard.vendor === 'string' &&
27
+ 'validate' in standard &&
28
+ typeof standard.validate === 'function');
29
+ }
30
+ catch {
31
+ return false;
32
+ }
33
+ }
34
+ /**
35
+ * The verdict a schema's result states, or `undefined` for a value that is not a Standard Schema
36
+ * result: a success must hold its own `value` and no issues, and a failure an issues array. Both
37
+ * `issue` and `read` read a result here, so neither keeps parameters the schema never output. A
38
+ * getter or a proxy trap that throws while the result is read propagates to the caller.
39
+ */
40
+ function verdictOf(result) {
41
+ if (!isObjectLike(result)) {
42
+ return undefined;
43
+ }
44
+ if (result.issues === undefined) {
45
+ return Object.hasOwn(result, 'value') ? { kind: 'accepted', params: result.value } : undefined;
46
+ }
47
+ const issues = result.issues;
48
+ return Array.isArray(issues) ? { kind: 'rejected' } : undefined;
49
+ }
50
+ /** The verdict a result states, where a result that throws while it is read states none. */
51
+ function verdictOrNothing(result) {
52
+ try {
53
+ return verdictOf(result);
54
+ }
55
+ catch {
56
+ return undefined;
57
+ }
58
+ }
59
+ /**
60
+ * Whether a schema's answer is a promise or another thenable, which a synchronous read cannot
61
+ * wait for. A value whose `then` cannot be read is not a thenable, so the test never throws.
62
+ */
63
+ function isThenable(value) {
64
+ try {
65
+ return isObjectLike(value) && 'then' in value && typeof value.then === 'function';
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ }
71
+ /** The `issueCode()` call a fault marks one part of: the code, the config, or one of its keys. */
72
+ function declarationAt(code, config, mark) {
73
+ return { arguments: [code, config], factory: 'issueCode', mark };
74
+ }
75
+ /** The mark for one key of a config object, or the config itself when the key is absent. */
76
+ function keyMark(config, key) {
77
+ return key in config ? `1.${key}` : '1';
78
+ }
79
+ /** Faults on a declaration that bypassed the types: a code outside the grammar, or a bad config. */
80
+ function checkDeclaration(code, config) {
81
+ if (!isRuleIdentity(code)) {
82
+ throw fault(issueCodeName, declarationAt(code, config, '0'), {
83
+ correction: 'Supply a code such as "@acme/validators/port-range", with each subpath segment and the rule of lowercase letters and digits in words joined by single hyphens.',
84
+ sentence: `issueCode() code ${quote(code)} is not a package name, any subpath segments, and a rule name joined by slashes.`,
85
+ });
86
+ }
87
+ if (!isPlainObject(config)) {
88
+ throw fault(issueCodeConfig, declarationAt(code, config, '1'), {
89
+ correction: 'Supply an object with a schema and a message function.',
90
+ sentence: 'issueCode() config is not a plain object.',
91
+ });
92
+ }
93
+ if (!isStandardSchema(config.schema)) {
94
+ throw fault(issueCodeConfig, declarationAt(code, config, keyMark(config, 'schema')), {
95
+ correction: 'Supply a Standard Schema value that validates the parameters.',
96
+ sentence: 'issueCode() schema is not a Standard Schema.',
97
+ });
98
+ }
99
+ if (typeof config.message !== 'function') {
100
+ throw fault(issueCodeConfig, declarationAt(code, config, keyMark(config, 'message')), {
101
+ correction: 'Supply a function that builds the sentence from the parameters.',
102
+ sentence: 'issueCode() message is not a function.',
103
+ });
104
+ }
105
+ }
106
+ /**
107
+ * Declares one issue code with its parameter schema and its one sentence, and returns a frozen
108
+ * descriptor. `issue` builds the rejection a `parse` function returns, and `read` recovers the
109
+ * typed parameters from an issue carrying the code.
110
+ */
111
+ function issueCode(code, config) {
112
+ checkDeclaration(code, config);
113
+ const { message, schema } = config;
114
+ // A schema fault is the declaration's, so it marks the schema the code was declared with.
115
+ const schemaAt = declarationAt(code, config, '1.schema');
116
+ /** The schema's synchronous result; a promise faults, since neither caller can wait for it. */
117
+ function settled(answer) {
118
+ if (isThenable(answer)) {
119
+ // The unawaited answer may still reject, and an unobserved rejection would end the process.
120
+ void Promise.resolve(answer).catch(() => undefined);
121
+ throw fault(issueCodeSchema, schemaAt, {
122
+ correction: 'Supply a schema that validates synchronously.',
123
+ sentence: `Issue code ${JSON.stringify(code)} has a schema that answers with a promise.`,
124
+ });
125
+ }
126
+ return answer;
127
+ }
128
+ /** The schema's answer to the parameters `issue` was given; a throw is the declaration's fault. */
129
+ function declaredAnswer(params) {
130
+ try {
131
+ return schema['~standard'].validate(params);
132
+ }
133
+ catch {
134
+ throw fault(issueCodeSchema, schemaAt, {
135
+ correction: 'Supply a schema that returns its issues instead of throwing.',
136
+ sentence: `Issue code ${JSON.stringify(code)} has a schema that throws in issue().`,
137
+ });
138
+ }
139
+ }
140
+ /**
141
+ * The verdict `issue` builds from. Anything but a well-formed synchronous result is the
142
+ * declaration's fault, so a result that throws while it is read is not a result either.
143
+ */
144
+ function declaredVerdict(params) {
145
+ const verdict = verdictOrNothing(settled(declaredAnswer(params)));
146
+ if (verdict === undefined) {
147
+ throw fault(issueCodeSchema, schemaAt, {
148
+ correction: 'Supply a schema that returns its value or its issues.',
149
+ sentence: `Issue code ${JSON.stringify(code)} has a schema that answers with a value that is not a Standard Schema result.`,
150
+ });
151
+ }
152
+ return verdict;
153
+ }
154
+ return Object.freeze({
155
+ code,
156
+ issue: (params) => {
157
+ const verdict = declaredVerdict(params);
158
+ if (verdict.kind === 'rejected') {
159
+ throw fault(issueParameters, { arguments: [params], factory: 'issue', mark: '0' }, {
160
+ correction: 'Supply parameters its schema accepts.',
161
+ sentence: `Issue code ${JSON.stringify(code)} rejects the parameters passed to issue().`,
162
+ });
163
+ }
164
+ return Object.freeze({ code, message: message(verdict.params), params: verdict.params });
165
+ },
166
+ // Core hands a failure view a plain-object copy of each issue a validator returned.
167
+ // Reading the copy's own fields runs no getter, and its parameters reach the schema as they are.
168
+ read: (issue) => {
169
+ const candidate = issue;
170
+ if (!isObjectLike(candidate) || !('code' in candidate) || candidate.code !== code) {
171
+ return undefined;
172
+ }
173
+ const params = 'params' in candidate ? candidate.params : undefined;
174
+ if (!isObjectLike(params)) {
175
+ return undefined;
176
+ }
177
+ const verdict = verdictOf(settled(schema['~standard'].validate(params)));
178
+ return verdict?.kind === 'accepted' ? verdict.params : undefined;
179
+ },
180
+ schema,
181
+ });
182
+ }
183
+ export { issueCode };
@@ -0,0 +1,6 @@
1
+ import type { StandardSchemaV1 } from '@loomcli/core';
2
+ /** A rejection: one issue with no path, carrying the configuration's code and one sentence. */
3
+ declare function reject(issue: StandardSchemaV1.Issue): StandardSchemaV1.FailureResult;
4
+ /** Joins items as a sentence lists them: `a`, `a or b`, or `a, b, or c`. */
5
+ declare function listing(items: readonly string[]): string;
6
+ export { listing, reject };
package/dist/issues.js ADDED
@@ -0,0 +1,17 @@
1
+ /** The longest list that joins with `or` alone. */
2
+ const pair = 2;
3
+ const first = 0;
4
+ /** The index of the last item, counted from the end. */
5
+ const last = -1;
6
+ /** A rejection: one issue with no path, carrying the configuration's code and one sentence. */
7
+ function reject(issue) {
8
+ return { issues: [issue] };
9
+ }
10
+ /** Joins items as a sentence lists them: `a`, `a or b`, or `a, b, or c`. */
11
+ function listing(items) {
12
+ if (items.length <= pair) {
13
+ return items.join(' or ');
14
+ }
15
+ return `${items.slice(first, last).join(', ')}, or ${items.at(last) ?? ''}`;
16
+ }
17
+ export { listing, reject };
@@ -0,0 +1,9 @@
1
+ import type { Validator } from './create.js';
2
+ interface NumberOptions {
3
+ min?: number;
4
+ max?: number;
5
+ }
6
+ /** A finite decimal number, with an optional fraction and exponent, within `min` and `max`. */
7
+ declare function number(options?: NumberOptions): Validator<number>;
8
+ export { number };
9
+ export type { NumberOptions };
package/dist/number.js ADDED
@@ -0,0 +1,26 @@
1
+ import { boundsIssue, boundsSchema, readBounds, withinBounds, withoutNegativeZero, } from './bounds.js';
2
+ import { numberCodes } from './codes.js';
3
+ import { createValidator } from './create.js';
4
+ import { reject } from './issues.js';
5
+ const decimal = /^-?[0-9]+(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?$/u;
6
+ /** A finite decimal number, with an optional fraction and exponent, within `min` and `max`. */
7
+ function number(options) {
8
+ const bounds = readBounds(options, {
9
+ accepts: Number.isFinite,
10
+ correction: 'Supply a finite number.',
11
+ factory: 'number',
12
+ requirement: 'a finite number',
13
+ });
14
+ const issue = boundsIssue(numberCodes, bounds);
15
+ return createValidator({
16
+ inputSchema: boundsSchema('number', bounds),
17
+ parse: (raw) => {
18
+ if (!decimal.test(raw)) {
19
+ return reject(issue);
20
+ }
21
+ const value = withoutNegativeZero(Number(raw));
22
+ return Number.isFinite(value) && withinBounds(value, bounds) ? { value } : reject(issue);
23
+ },
24
+ });
25
+ }
26
+ export { number };
@@ -0,0 +1,4 @@
1
+ import type { Validator } from './create.js';
2
+ /** A token exactly equal to one of the declared values, typed as their union. */
3
+ declare function oneOf<const Values extends readonly [string, ...string[]]>(values: Values): Validator<Values[number]>;
4
+ export { oneOf };
package/dist/one-of.js ADDED
@@ -0,0 +1,79 @@
1
+ import { DeclarationError } from '@loomcli/core';
2
+ import { oneOfIssue } from './codes.js';
3
+ import { createValidator } from './create.js';
4
+ import { fault } from './faults.js';
5
+ import { reject } from './issues.js';
6
+ import { oneOfValues } from './rules.js';
7
+ const empty = 0;
8
+ /** A fault of the list `oneOf()` received, marking the list or one of its values. */
9
+ function listFault(values, mark, parts) {
10
+ return fault(oneOfValues, { arguments: [values], factory: 'oneOf', mark }, parts);
11
+ }
12
+ /** The declared list as unknown items, after faulting on one that is not an array or is empty. */
13
+ function checkList(values) {
14
+ if (!Array.isArray(values)) {
15
+ throw listFault(values, '0', {
16
+ correction: 'Supply an array of strings.',
17
+ sentence: 'oneOf() values is not an array.',
18
+ });
19
+ }
20
+ const items = values;
21
+ if (items.length === empty) {
22
+ throw listFault(values, '0', {
23
+ correction: 'List at least one value.',
24
+ sentence: 'oneOf() values is empty.',
25
+ });
26
+ }
27
+ return items;
28
+ }
29
+ /**
30
+ * Faults on a value that is not a string, is empty, or was listed earlier. `seen` holds the
31
+ * position of each value listed so far, so a repeat marks both.
32
+ */
33
+ function checkValue(listed, index, seen) {
34
+ const item = listed[index];
35
+ if (typeof item !== 'string') {
36
+ throw listFault(listed, `0.${String(index)}`, {
37
+ correction: 'List strings only.',
38
+ sentence: 'oneOf() lists a value that is not a string.',
39
+ });
40
+ }
41
+ if (item === '') {
42
+ throw listFault(listed, `0.${String(index)}`, {
43
+ correction: 'List nonempty values only.',
44
+ sentence: 'oneOf() lists an empty string.',
45
+ });
46
+ }
47
+ const first = seen.get(item);
48
+ if (first !== undefined) {
49
+ const call = { arguments: [listed], call: 'oneOf' };
50
+ throw new DeclarationError(oneOfValues, {
51
+ correction: 'List each value once.',
52
+ findings: [
53
+ { ...call, mark: `0.${String(first)}`, note: 'the first' },
54
+ { ...call, mark: `0.${String(index)}`, note: 'the second' },
55
+ ],
56
+ sentence: `oneOf() lists ${JSON.stringify(item)} twice.`,
57
+ });
58
+ }
59
+ seen.set(item, index);
60
+ }
61
+ /** A token exactly equal to one of the declared values, typed as their union. */
62
+ function oneOf(values) {
63
+ checkList(values);
64
+ // The copy is what the validator keeps, so the checks read the copy and not the caller's list.
65
+ const listed = [...values];
66
+ const seen = new Map();
67
+ for (const index of listed.keys()) {
68
+ checkValue(listed, index, seen);
69
+ }
70
+ const issue = oneOfIssue.issue({ values: listed });
71
+ return createValidator({
72
+ inputSchema: { type: 'string', enum: [...listed] },
73
+ parse: (raw) => {
74
+ const match = listed.find((value) => value === raw);
75
+ return match === undefined ? reject(issue) : { value: match };
76
+ },
77
+ });
78
+ }
79
+ export { oneOf };
@@ -0,0 +1,54 @@
1
+ import type { StandardSchemaV1 } from '@loomcli/core';
2
+ import type { PathKind } from './probe.js';
3
+ /** A check on one parameter that also tells the compiler its type. */
4
+ type Guard<Value> = (value: unknown) => value is Value;
5
+ /** The parameters of a code whose sentence has no blank. */
6
+ type NoParams = Readonly<Record<string, never>>;
7
+ declare const isCount: Guard<number>;
8
+ declare const isSafeInteger: Guard<number>;
9
+ declare const isFiniteNumber: Guard<number>;
10
+ declare const noParams: StandardSchemaV1<Readonly<Record<string, never>>, Readonly<Record<string, never>>>;
11
+ declare function minParams(accepts: Guard<number>): StandardSchemaV1<{
12
+ min: number;
13
+ }, {
14
+ min: number;
15
+ }>;
16
+ declare function maxParams(accepts: Guard<number>): StandardSchemaV1<{
17
+ max: number;
18
+ }, {
19
+ max: number;
20
+ }>;
21
+ declare function rangeParams(accepts: Guard<number>): StandardSchemaV1<{
22
+ max: number;
23
+ min: number;
24
+ }, {
25
+ max: number;
26
+ min: number;
27
+ }>;
28
+ declare const lengthParams: StandardSchemaV1<{
29
+ length: number;
30
+ }, {
31
+ length: number;
32
+ }>;
33
+ declare const messageParams: StandardSchemaV1<{
34
+ message: string;
35
+ }, {
36
+ message: string;
37
+ }>;
38
+ declare const valuesParams: StandardSchemaV1<{
39
+ values: readonly string[];
40
+ }, {
41
+ values: readonly string[];
42
+ }>;
43
+ declare const protocolsParams: StandardSchemaV1<{
44
+ protocols: readonly string[];
45
+ }, {
46
+ protocols: readonly string[];
47
+ }>;
48
+ declare const kindParams: StandardSchemaV1<{
49
+ kind: PathKind;
50
+ }, {
51
+ kind: PathKind;
52
+ }>;
53
+ export { isCount, isFiniteNumber, isSafeInteger, kindParams, lengthParams, maxParams, messageParams, minParams, noParams, protocolsParams, rangeParams, valuesParams, };
54
+ export type { NoParams };
package/dist/params.js ADDED
@@ -0,0 +1,68 @@
1
+ import { vendor } from './create.js';
2
+ import { isPlainObject } from './data.js';
3
+ /**
4
+ * A Standard Schema for one fixed parameter shape, built without a schema library. It accepts a
5
+ * plain object with exactly `keys`, which `read` turns into the typed parameters, and outputs a
6
+ * frozen copy, so an issue's parameters cannot be changed by a reader.
7
+ */
8
+ function paramsSchema(shape, keys, read) {
9
+ const refusal = () => ({
10
+ issues: [{ message: `Expected the parameters ${shape}.` }],
11
+ });
12
+ return Object.freeze({
13
+ '~standard': Object.freeze({
14
+ validate: (value) => {
15
+ if (!isPlainObject(value)) {
16
+ return refusal();
17
+ }
18
+ const exact = Object.keys(value).length === keys.length &&
19
+ keys.every((key) => Object.hasOwn(value, key));
20
+ const params = exact ? read(value) : undefined;
21
+ if (params === undefined) {
22
+ return refusal();
23
+ }
24
+ Object.freeze(params);
25
+ return { value: params };
26
+ },
27
+ vendor,
28
+ version: 1,
29
+ }),
30
+ });
31
+ }
32
+ const none = 0;
33
+ const isCount = (value) => typeof value === 'number' && Number.isSafeInteger(value) && value >= none;
34
+ const isSafeInteger = (value) => typeof value === 'number' && Number.isSafeInteger(value);
35
+ const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
36
+ const isKind = (value) => value === 'file' || value === 'directory' || value === 'any';
37
+ /** A nonempty list of strings, copied and frozen, or undefined for anything else. */
38
+ function stringList(value) {
39
+ if (!Array.isArray(value)) {
40
+ return undefined;
41
+ }
42
+ const items = value;
43
+ return items.length > none && items.every((item) => typeof item === 'string')
44
+ ? Object.freeze([...items])
45
+ : undefined;
46
+ }
47
+ const noParams = paramsSchema('{}', [], () => ({}));
48
+ function minParams(accepts) {
49
+ return paramsSchema('{ min: number }', ['min'], ({ min }) => accepts(min) ? { min } : undefined);
50
+ }
51
+ function maxParams(accepts) {
52
+ return paramsSchema('{ max: number }', ['max'], ({ max }) => accepts(max) ? { max } : undefined);
53
+ }
54
+ function rangeParams(accepts) {
55
+ return paramsSchema('{ min: number; max: number }', ['max', 'min'], ({ max, min }) => accepts(max) && accepts(min) ? { max, min } : undefined);
56
+ }
57
+ const lengthParams = paramsSchema('{ length: number }', ['length'], ({ length }) => isCount(length) ? { length } : undefined);
58
+ const messageParams = paramsSchema('{ message: string }', ['message'], ({ message }) => typeof message === 'string' && message !== '' ? { message } : undefined);
59
+ const valuesParams = paramsSchema('{ values: readonly string[] }', ['values'], (record) => {
60
+ const values = stringList(record.values);
61
+ return values === undefined ? undefined : { values };
62
+ });
63
+ const protocolsParams = paramsSchema('{ protocols: readonly string[] }', ['protocols'], (record) => {
64
+ const protocols = stringList(record.protocols);
65
+ return protocols === undefined ? undefined : { protocols };
66
+ });
67
+ const kindParams = paramsSchema("{ kind: 'file' | 'directory' | 'any' }", ['kind'], ({ kind }) => isKind(kind) ? { kind } : undefined);
68
+ export { isCount, isFiniteNumber, isSafeInteger, kindParams, lengthParams, maxParams, messageParams, minParams, noParams, protocolsParams, rangeParams, valuesParams, };
package/dist/path.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { Validator } from './create.js';
2
+ import type { PathKind } from './probe.js';
3
+ interface PathOptions {
4
+ access?: 'read' | 'write';
5
+ kind?: PathKind;
6
+ }
7
+ /**
8
+ * A path resolved against the host's cwd under the host platform's rules, and normalized.
9
+ * With `access`, the filesystem is probed; the probe is advisory, since the filesystem can change.
10
+ */
11
+ declare function path(options?: PathOptions): Validator<string>;
12
+ export { path };
13
+ export type { PathOptions };
package/dist/path.js ADDED
@@ -0,0 +1,64 @@
1
+ import { posix, win32 } from 'node:path';
2
+ import { pathIssue, pathReadableIssue, pathWritableIssue } from './codes.js';
3
+ import { createValidator } from './create.js';
4
+ import { fault, readOptions } from './faults.js';
5
+ import { reject } from './issues.js';
6
+ import { readable, writable } from './probe.js';
7
+ import { pathCheck } from './rules.js';
8
+ function accessOf(call, value) {
9
+ if (value === undefined || value === 'read' || value === 'write') {
10
+ return value;
11
+ }
12
+ throw fault(pathCheck, { ...call, mark: '0.access' }, {
13
+ correction: 'Supply "read" or "write".',
14
+ sentence: 'path() access is not "read" or "write".',
15
+ });
16
+ }
17
+ function kindOf(call, value, access) {
18
+ const at = { ...call, mark: '0.kind' };
19
+ if (value !== undefined && access === undefined) {
20
+ throw fault(pathCheck, at, {
21
+ correction: 'Supply an access or leave out kind.',
22
+ sentence: 'path() kind has no access to check.',
23
+ });
24
+ }
25
+ if (value === undefined || value === 'file' || value === 'directory' || value === 'any') {
26
+ return value ?? 'file';
27
+ }
28
+ throw fault(pathCheck, at, {
29
+ correction: 'Supply one of them.',
30
+ sentence: 'path() kind is not "file", "directory", or "any".',
31
+ });
32
+ }
33
+ /**
34
+ * A path resolved against the host's cwd under the host platform's rules, and normalized.
35
+ * With `access`, the filesystem is probed; the probe is advisory, since the filesystem can change.
36
+ */
37
+ function path(options) {
38
+ const declared = readOptions('path', options);
39
+ const call = { arguments: [options], factory: 'path' };
40
+ const access = accessOf(call, declared.access);
41
+ const kind = kindOf(call, declared.kind, access);
42
+ const issue = access === undefined
43
+ ? pathIssue.issue({})
44
+ : (access === 'read' ? pathReadableIssue : pathWritableIssue).issue({ kind });
45
+ return createValidator({
46
+ inputSchema: { type: 'string', minLength: 1 },
47
+ parse: (raw, context) => {
48
+ if (raw === '' || raw.includes('\0')) {
49
+ return reject(issue);
50
+ }
51
+ const { cwd, platform } = context.host;
52
+ const rules = platform === 'win32' ? win32 : posix;
53
+ const resolved = rules.resolve(cwd, raw);
54
+ if (access === undefined) {
55
+ return { value: resolved };
56
+ }
57
+ const probe = access === 'read'
58
+ ? readable(resolved, kind)
59
+ : writable(resolved, kind, rules.dirname(resolved));
60
+ return probe.then((passes) => (passes ? { value: resolved } : reject(issue)));
61
+ },
62
+ });
63
+ }
64
+ export { path };
package/dist/port.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { Validator } from './create.js';
2
+ /** A TCP or UDP port the operator names, 1 through 65535; port 0 is left to `integer()`. */
3
+ declare function port(): Validator<number>;
4
+ export { port };
package/dist/port.js ADDED
@@ -0,0 +1,18 @@
1
+ import { boundsSchema, withinBounds } from './bounds.js';
2
+ import { portIssue } from './codes.js';
3
+ import { createValidator } from './create.js';
4
+ import { readInteger } from './integer.js';
5
+ import { reject } from './issues.js';
6
+ const range = { max: 65_535, min: 1 };
7
+ /** A TCP or UDP port the operator names, 1 through 65535; port 0 is left to `integer()`. */
8
+ function port() {
9
+ const issue = portIssue.issue({});
10
+ return createValidator({
11
+ inputSchema: boundsSchema('integer', range),
12
+ parse: (raw) => {
13
+ const value = readInteger(raw);
14
+ return value !== undefined && withinBounds(value, range) ? { value } : reject(issue);
15
+ },
16
+ });
17
+ }
18
+ export { port };
@@ -0,0 +1,11 @@
1
+ type PathKind = 'file' | 'directory' | 'any';
2
+ /** Runs one probe, reading a verdict failure as `false` and rethrowing any other failure. */
3
+ declare function holds(probe: () => Promise<boolean>): Promise<boolean>;
4
+ declare function readable(target: string, kind: PathKind): Promise<boolean>;
5
+ /**
6
+ * Whether the process can write the entry, or create it when no entry exists: then the parent
7
+ * must be an existing directory the process can write. Nothing is created.
8
+ */
9
+ declare function writable(target: string, kind: PathKind, parent: string): Promise<boolean>;
10
+ export { holds, readable, writable };
11
+ export type { PathKind };