@atproto/lex-schema 0.0.15 → 0.0.16
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.
- package/CHANGELOG.md +9 -0
- package/dist/core/schema.d.ts +1 -1
- package/dist/core/schema.js +1 -1
- package/dist/core/schema.js.map +1 -1
- package/dist/core/string-format.d.ts +22 -4
- package/dist/core/string-format.d.ts.map +1 -1
- package/dist/core/string-format.js +43 -19
- package/dist/core/string-format.js.map +1 -1
- package/dist/core/validator.d.ts +13 -1
- package/dist/core/validator.d.ts.map +1 -1
- package/dist/core/validator.js +1 -0
- package/dist/core/validator.js.map +1 -1
- package/dist/schema/blob.d.ts +10 -8
- package/dist/schema/blob.d.ts.map +1 -1
- package/dist/schema/blob.js +39 -14
- package/dist/schema/blob.js.map +1 -1
- package/dist/schema/payload.d.ts +2 -2
- package/dist/schema/payload.d.ts.map +1 -1
- package/dist/schema/payload.js.map +1 -1
- package/dist/schema/string.js +1 -1
- package/dist/schema/string.js.map +1 -1
- package/package.json +3 -2
- package/src/core/schema.ts +1 -1
- package/src/core/string-format.ts +62 -16
- package/src/core/validator.ts +17 -1
- package/src/schema/blob.test.ts +317 -49
- package/src/schema/blob.ts +56 -23
- package/src/schema/payload.ts +2 -2
- package/src/schema/string.test.ts +63 -0
- package/src/schema/string.ts +1 -1
package/dist/schema/blob.d.ts
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import { BlobRef,
|
|
1
|
+
import { BlobRef, LegacyBlobRef, isBlobRef, isLegacyBlobRef } from '@atproto/lex-data';
|
|
2
2
|
import { Schema, ValidationContext } from '../core.js';
|
|
3
3
|
/**
|
|
4
4
|
* Configuration options for blob schema validation.
|
|
5
|
-
*
|
|
6
|
-
* @property allowLegacy - Whether to allow legacy blob references format
|
|
7
|
-
* @property accept - List of accepted MIME types (supports wildcards like 'image/*' or '*\/*')
|
|
8
|
-
* @property maxSize - Maximum blob size in bytes
|
|
9
5
|
*/
|
|
10
|
-
export type BlobSchemaOptions =
|
|
6
|
+
export type BlobSchemaOptions = {
|
|
11
7
|
/**
|
|
12
8
|
* Whether to allow legacy blob references format
|
|
9
|
+
*
|
|
10
|
+
* @default false
|
|
13
11
|
* @see {@link LegacyBlobRef}
|
|
14
12
|
*/
|
|
15
13
|
allowLegacy?: boolean;
|
|
16
14
|
/**
|
|
17
|
-
* List of accepted
|
|
15
|
+
* List of accepted MIME types (supports wildcards like 'image/*' or '*\/*')
|
|
16
|
+
*
|
|
17
|
+
* @default undefined // accepts all MIME types
|
|
18
18
|
*/
|
|
19
19
|
accept?: string[];
|
|
20
20
|
/**
|
|
21
|
-
* Maximum size in bytes
|
|
21
|
+
* Maximum blob size in bytes
|
|
22
|
+
*
|
|
23
|
+
* @default undefined // no size limit
|
|
22
24
|
*/
|
|
23
25
|
maxSize?: number;
|
|
24
26
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blob.d.ts","sourceRoot":"","sources":["../../src/schema/blob.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,
|
|
1
|
+
{"version":3,"file":"blob.d.ts","sourceRoot":"","sources":["../../src/schema/blob.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,aAAa,EACb,SAAS,EACT,eAAe,EAEhB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,MAAM,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAA;AAGtD;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG;IAC9B;;;;;OAKG;IACH,WAAW,CAAC,EAAE,OAAO,CAAA;IAErB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,EAAE,CAAA;IAEjB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAED,YAAY,EAAE,OAAO,EAAE,aAAa,EAAE,CAAA;AACtC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,CAAA;AAErC;;;;;;;;;;;;;;GAcG;AACH,qBAAa,UAAU,CACrB,KAAK,CAAC,QAAQ,SAAS,iBAAiB,GAAG,WAAW,CAAC,OAAO,CAAC,CAC/D,SAAQ,MAAM,CACd,QAAQ,SAAS;IAAE,WAAW,EAAE,IAAI,CAAA;CAAE,GAAG,OAAO,GAAG,aAAa,GAAG,OAAO,CAC3E;IAGa,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ;IAFvC,QAAQ,CAAC,IAAI,EAAG,MAAM,CAAS;gBAEV,OAAO,CAAC,EAAE,QAAQ,YAAA;IAIvC,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB;IAwBxD,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO;CAKnC;AA6CD;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,IAAI,GACf,CAAC,SAAS,iBAAiB;kBAAmB,KAAK;aACzC,CAAC,kBAEX,CAAA"}
|
package/dist/schema/blob.js
CHANGED
|
@@ -29,23 +29,21 @@ class BlobSchema extends core_js_1.Schema {
|
|
|
29
29
|
this.options = options;
|
|
30
30
|
}
|
|
31
31
|
validateInContext(input, ctx) {
|
|
32
|
-
const blob = input
|
|
33
|
-
? (0, lex_data_1.isBlobRef)(input, this.options)
|
|
34
|
-
? input
|
|
35
|
-
: null
|
|
36
|
-
: this.options?.allowLegacy === true && (0, lex_data_1.isLegacyBlobRef)(input)
|
|
37
|
-
? input
|
|
38
|
-
: null;
|
|
32
|
+
const blob = parseValue.call(ctx, input, this.options);
|
|
39
33
|
if (!blob) {
|
|
40
34
|
return ctx.issueUnexpectedType(input, 'blob');
|
|
41
35
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
36
|
+
// In non-strict mode, we allow blob refs to pass through without MIME
|
|
37
|
+
// type or size checks.
|
|
38
|
+
if (ctx.options.strict) {
|
|
39
|
+
const accept = this.options?.accept;
|
|
40
|
+
if (accept && !matchesMime(blob.mimeType, accept)) {
|
|
41
|
+
return ctx.issueInvalidPropertyValue(blob, 'mimeType', accept);
|
|
42
|
+
}
|
|
43
|
+
const maxSize = this.options?.maxSize;
|
|
44
|
+
if (maxSize != null && 'size' in blob && blob.size > maxSize) {
|
|
45
|
+
return ctx.issueTooBig(blob, 'blob', maxSize, blob.size);
|
|
46
|
+
}
|
|
49
47
|
}
|
|
50
48
|
return ctx.success(blob);
|
|
51
49
|
}
|
|
@@ -57,6 +55,33 @@ class BlobSchema extends core_js_1.Schema {
|
|
|
57
55
|
}
|
|
58
56
|
}
|
|
59
57
|
exports.BlobSchema = BlobSchema;
|
|
58
|
+
function parseValue(input, options) {
|
|
59
|
+
// If there is a $type property, we treat if as a potential BlobRef and
|
|
60
|
+
// validate accordingly.
|
|
61
|
+
if (input?.$type !== undefined) {
|
|
62
|
+
// Use the context's option for the "strict" check
|
|
63
|
+
return (0, lex_data_1.isBlobRef)(input, this.options) ? input : null;
|
|
64
|
+
}
|
|
65
|
+
// If there is no $type property, we may be dealing with a legacy blob ref. If
|
|
66
|
+
// legacy refs are allowed, validate against the legacy format. If not
|
|
67
|
+
// allowed, but we are in non-strict "parse" mode, coerce legacy refs into
|
|
68
|
+
// standard BlobRef format for backward compatibility. Otherwise, reject the
|
|
69
|
+
// value.
|
|
70
|
+
if (options?.allowLegacy) {
|
|
71
|
+
if ((0, lex_data_1.isLegacyBlobRef)(input)) {
|
|
72
|
+
return input;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (!this.options.strict && this.options.mode === 'parse') {
|
|
76
|
+
if ((0, lex_data_1.isLegacyBlobRef)(input)) {
|
|
77
|
+
const { cid, mimeType } = input;
|
|
78
|
+
const ref = (0, lex_data_1.parseCidSafe)(cid);
|
|
79
|
+
if (ref)
|
|
80
|
+
return { $type: 'blob', ref, mimeType, size: -1 };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
60
85
|
function matchesMime(mime, accepted) {
|
|
61
86
|
if (accepted.includes('*/*'))
|
|
62
87
|
return true;
|
package/dist/schema/blob.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"blob.js","sourceRoot":"","sources":["../../src/schema/blob.ts"],"names":[],"mappings":";;;AAAA,gDAM0B;
|
|
1
|
+
{"version":3,"file":"blob.js","sourceRoot":"","sources":["../../src/schema/blob.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n BlobRef,\n LegacyBlobRef,\n isBlobRef,\n isLegacyBlobRef,\n parseCidSafe,\n} from '@atproto/lex-data'\nimport { Schema, ValidationContext } from '../core.js'\nimport { memoizedOptions } from '../util/memoize.js'\n\n/**\n * Configuration options for blob schema validation.\n */\nexport type BlobSchemaOptions = {\n /**\n * Whether to allow legacy blob references format\n *\n * @default false\n * @see {@link LegacyBlobRef}\n */\n allowLegacy?: boolean\n\n /**\n * List of accepted MIME types (supports wildcards like 'image/*' or '*\\/*')\n *\n * @default undefined // accepts all MIME types\n */\n accept?: string[]\n\n /**\n * Maximum blob size in bytes\n *\n * @default undefined // no size limit\n */\n maxSize?: number\n}\n\nexport type { BlobRef, LegacyBlobRef }\nexport { isBlobRef, isLegacyBlobRef }\n\n/**\n * Schema for validating blob references in AT Protocol.\n *\n * Validates BlobRef objects which contain a CID reference to binary data,\n * along with metadata like MIME type and size. Can optionally accept\n * legacy blob reference format.\n *\n * @template TOptions - The configuration options type\n *\n * @example\n * ```ts\n * const schema = new BlobSchema({ accept: ['image/*'], maxSize: 1000000 })\n * const result = schema.validate(blobRef)\n * ```\n */\nexport class BlobSchema<\n const TOptions extends BlobSchemaOptions = NonNullable<unknown>,\n> extends Schema<\n TOptions extends { allowLegacy: true } ? BlobRef | LegacyBlobRef : BlobRef\n> {\n readonly type = 'blob' as const\n\n constructor(readonly options?: TOptions) {\n super()\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n const blob = parseValue.call(ctx, input, this.options)\n\n if (!blob) {\n return ctx.issueUnexpectedType(input, 'blob')\n }\n\n // In non-strict mode, we allow blob refs to pass through without MIME\n // type or size checks.\n if (ctx.options.strict) {\n const accept = this.options?.accept\n if (accept && !matchesMime(blob.mimeType, accept)) {\n return ctx.issueInvalidPropertyValue(blob, 'mimeType', accept)\n }\n\n const maxSize = this.options?.maxSize\n if (maxSize != null && 'size' in blob && blob.size > maxSize) {\n return ctx.issueTooBig(blob, 'blob', maxSize, blob.size)\n }\n }\n\n return ctx.success(blob)\n }\n\n matchesMime(mime: string): boolean {\n const accept = this.options?.accept\n if (!accept) return true\n return matchesMime(mime, accept)\n }\n}\n\nfunction parseValue(\n this: ValidationContext,\n input: unknown,\n options?: BlobSchemaOptions,\n): BlobRef | LegacyBlobRef | null {\n // If there is a $type property, we treat if as a potential BlobRef and\n // validate accordingly.\n if ((input as any)?.$type !== undefined) {\n // Use the context's option for the \"strict\" check\n return isBlobRef(input, this.options) ? input : null\n }\n\n // If there is no $type property, we may be dealing with a legacy blob ref. If\n // legacy refs are allowed, validate against the legacy format. If not\n // allowed, but we are in non-strict \"parse\" mode, coerce legacy refs into\n // standard BlobRef format for backward compatibility. Otherwise, reject the\n // value.\n if (options?.allowLegacy) {\n if (isLegacyBlobRef(input)) {\n return input\n }\n } else if (!this.options.strict && this.options.mode === 'parse') {\n if (isLegacyBlobRef(input)) {\n const { cid, mimeType } = input\n const ref = parseCidSafe(cid)\n if (ref) return { $type: 'blob', ref, mimeType, size: -1 }\n }\n }\n\n return null\n}\n\nfunction matchesMime(mime: string, accepted: string[]): boolean {\n if (accepted.includes('*/*')) return true\n if (accepted.includes(mime)) return true\n for (const value of accepted) {\n if (value.endsWith('/*') && mime.startsWith(value.slice(0, -1))) {\n return true\n }\n }\n return false\n}\n\n/**\n * Creates a blob schema for validating blob references with optional constraints.\n *\n * Blob references are used in AT Protocol to reference binary data stored\n * separately from records. They contain a CID, MIME type, and size information.\n *\n * @param options - Optional configuration for MIME type filtering and size limits\n * @returns A new {@link BlobSchema} instance\n *\n * @example\n * ```ts\n * // Basic blob reference\n * const fileSchema = l.blob()\n *\n * // Image files only\n * const imageSchema = l.blob({ accept: ['image/png', 'image/jpeg', 'image/gif'] })\n *\n * // Any image type with size limit\n * const avatarSchema = l.blob({ accept: ['image/*'], maxSize: 1000000 })\n *\n * // Allow legacy format\n * const legacySchema = l.blob({ allowLegacy: true })\n * ```\n */\nexport const blob = /*#__PURE__*/ memoizedOptions(function <\n O extends BlobSchemaOptions = { allowLegacy?: false },\n>(options?: O) {\n return new BlobSchema(options)\n})\n"]}
|
package/dist/schema/payload.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { LexValue } from '@atproto/lex-data';
|
|
2
|
-
import {
|
|
2
|
+
import { InferInput, Schema, Validator } from '../core.js';
|
|
3
3
|
import { ObjectSchema } from './object.js';
|
|
4
4
|
export type { LexValue };
|
|
5
5
|
type ToBodyMime<TEncoding extends string> = TEncoding extends '*/*' ? `${string}/${string}` : TEncoding extends `${infer T extends string}/*` ? `${T}/${string}` : TEncoding;
|
|
6
|
-
type ToBodyType<TEncoding extends string, TSchema, TBinary> = TSchema extends Schema ?
|
|
6
|
+
type ToBodyType<TEncoding extends string, TSchema, TBinary> = TSchema extends Schema ? InferInput<TSchema> : TEncoding extends `application/json` ? LexValue : TBinary;
|
|
7
7
|
/**
|
|
8
8
|
* Infers the type of a Payload's encoding and body.
|
|
9
9
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/schema/payload.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAC5C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"payload.d.ts","sourceRoot":"","sources":["../../src/schema/payload.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payload.js","sourceRoot":"","sources":["../../src/schema/payload.ts"],"names":[],"mappings":";;;AAuKA,0BAKC;AA4BD,kCAIC;AA1MD,2CAAkD;AAwElD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,OAAO;IAKP;IACA;IAFX,YACW,QAAmB,EACnB,MAAe;QADf,aAAQ,GAAR,QAAQ,CAAW;QACnB,WAAM,GAAN,MAAM,CAAS;QAExB,IAAI,QAAQ,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACnD,MAAM,IAAI,SAAS,CAAC,qDAAqD,CAAC,CAAA;QAC5E,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,eAAe,CAAC,WAA+B;QAC7C,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAA;QAEzB,yBAAyB;QACzB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,oBAAoB;YACpB,OAAO,WAAW,IAAI,IAAI,CAAA;QAC5B,CAAC;aAAM,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;YAC/B,4CAA4C;YAC5C,OAAO,KAAK,CAAA;QACd,CAAC;QAED,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;YACvB,OAAO,IAAI,CAAA;QACb,CAAC;QAED,MAAM,IAAI,GAAG,WAAW,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QACjD,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/C,CAAC;QAED,gEAAgE;QAChE,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAA;QACd,CAAC;QAED,OAAO,QAAQ,KAAK,IAAI,CAAA;IAC1B,CAAC;CACF;AA7CD,0BA6CC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAwB;AACxB,SAAgB,OAAO,CAGrB,WAAc,SAAc,EAAE,YAAe,SAAc;IAC3D,OAAO,IAAI,OAAO,CAAO,QAAQ,EAAE,SAAS,CAAC,CAAA;AAC/C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAwB;AACxB,SAAgB,WAAW,CAEzB,UAAa;IACb,OAAO,OAAO,CAAC,kBAAkB,EAAE,IAAA,kBAAM,EAAC,UAAU,CAAC,CAAC,CAAA;AACxD,CAAC","sourcesContent":["import { LexValue } from '@atproto/lex-data'\nimport {
|
|
1
|
+
{"version":3,"file":"payload.js","sourceRoot":"","sources":["../../src/schema/payload.ts"],"names":[],"mappings":";;;AAuKA,0BAKC;AA4BD,kCAIC;AA1MD,2CAAkD;AAwElD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAa,OAAO;IAKP;IACA;IAFX,YACW,QAAmB,EACnB,MAAe;QADf,aAAQ,GAAR,QAAQ,CAAW;QACnB,WAAM,GAAN,MAAM,CAAS;QAExB,IAAI,QAAQ,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACnD,MAAM,IAAI,SAAS,CAAC,qDAAqD,CAAC,CAAA;QAC5E,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,eAAe,CAAC,WAA+B;QAC7C,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,CAAA;QAEzB,yBAAyB;QACzB,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;YAC3B,oBAAoB;YACpB,OAAO,WAAW,IAAI,IAAI,CAAA;QAC5B,CAAC;aAAM,IAAI,WAAW,IAAI,IAAI,EAAE,CAAC;YAC/B,4CAA4C;YAC5C,OAAO,KAAK,CAAA;QACd,CAAC;QAED,IAAI,QAAQ,KAAK,KAAK,EAAE,CAAC;YACvB,OAAO,IAAI,CAAA;QACb,CAAC;QAED,MAAM,IAAI,GAAG,WAAW,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QACjD,IAAI,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/C,CAAC;QAED,gEAAgE;QAChE,IAAI,QAAQ,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAA;QACd,CAAC;QAED,OAAO,QAAQ,KAAK,IAAI,CAAA;IAC1B,CAAC;CACF;AA7CD,0BA6CC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAwB;AACxB,SAAgB,OAAO,CAGrB,WAAc,SAAc,EAAE,YAAe,SAAc;IAC3D,OAAO,IAAI,OAAO,CAAO,QAAQ,EAAE,SAAS,CAAC,CAAA;AAC/C,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAwB;AACxB,SAAgB,WAAW,CAEzB,UAAa;IACb,OAAO,OAAO,CAAC,kBAAkB,EAAE,IAAA,kBAAM,EAAC,UAAU,CAAC,CAAC,CAAA;AACxD,CAAC","sourcesContent":["import { LexValue } from '@atproto/lex-data'\nimport { InferInput, Schema, Validator } from '../core.js'\nimport { ObjectSchema, object } from './object.js'\n\nexport type { LexValue }\n\ntype ToBodyMime<TEncoding extends string> = TEncoding extends '*/*'\n ? `${string}/${string}`\n : TEncoding extends `${infer T extends string}/*`\n ? `${T}/${string}`\n : TEncoding\n\ntype ToBodyType<\n TEncoding extends string,\n TSchema,\n TBinary,\n> = TSchema extends Schema\n ? InferInput<TSchema>\n : TEncoding extends `application/json`\n ? LexValue\n : TBinary\n\n/**\n * Infers the type of a Payload's encoding and body.\n *\n * @template TPayload - The Payload type\n * @template TBody - Fallback body type for non-JSON encodings\n */\nexport type InferPayload<TPayload extends Payload, TBinary> =\n TPayload extends Payload<infer TEncoding, infer TSchema>\n ? TEncoding extends string\n ? {\n encoding: ToBodyMime<TEncoding>\n body: ToBodyType<TEncoding, TSchema, TBinary>\n }\n : undefined\n : never\n\n/**\n * Converts schema encoding patterns to data encoding types.\n *\n * Handles wildcards like '*\\/*' and 'image/*' in MIME types.\n *\n * @template TPayload - The Payload type\n */\nexport type InferPayloadEncoding<TPayload extends Payload> =\n TPayload extends Payload<infer TEncoding, any>\n ? TEncoding extends string\n ? ToBodyMime<TEncoding>\n : undefined\n : never\n\n/**\n * Infers the body type from a Payload and fallback type.\n *\n * @template TPayload - The Payload type\n * @template TBody - Fallback body type for non-JSON encodings without schema\n */\nexport type InferPayloadBody<TPayload extends Payload, TBinary> =\n TPayload extends Payload<infer TEncoding, infer TSchema>\n ? TEncoding extends string\n ? ToBodyType<TEncoding, TSchema, TBinary>\n : undefined\n : never\n\n/**\n * Determines valid schema type based on encoding presence.\n *\n * @template E - The encoding string type, or undefined\n */\nexport type PayloadSchema<E extends string | undefined> = E extends undefined\n ? undefined\n : Schema<LexValue> | undefined\n\n/**\n * Represents a payload definition for Lexicon endpoints.\n *\n * Payloads define the body format for HTTP requests and responses.\n * They consist of an encoding (MIME type) and an optional schema\n * for validating the body content.\n *\n * @template TEncoding - The MIME type string, or undefined for no body\n * @template TPayload - The schema type for body validation\n *\n * @example\n * ```ts\n * const jsonPayload = new Payload('application/json', l.object({ data: l.string() }))\n * const binaryPayload = new Payload('image/*', undefined)\n * const noPayload = new Payload(undefined, undefined)\n * ```\n */\nexport class Payload<\n const TEncoding extends string | undefined = string | undefined,\n const TSchema extends PayloadSchema<TEncoding> = PayloadSchema<TEncoding>,\n> {\n constructor(\n readonly encoding: TEncoding,\n readonly schema: TSchema,\n ) {\n if (encoding === undefined && schema !== undefined) {\n throw new TypeError('schema cannot be defined when encoding is undefined')\n }\n }\n\n /**\n * Checks whether the given content-type matches the expected payload schema's\n * encoding.\n */\n matchesEncoding(contentType: string | undefined): boolean {\n const { encoding } = this\n\n // Handle undefined cases\n if (encoding === undefined) {\n // Expecting no body\n return contentType == null\n } else if (contentType == null) {\n // Expecting a body, but got no content-type\n return false\n }\n\n if (encoding === '*/*') {\n return true\n }\n\n const mime = contentType?.split(';', 1)[0].trim()\n if (encoding.endsWith('/*')) {\n return mime.startsWith(encoding.slice(0, -1))\n }\n\n // Invalid: Lexicon can only specify \"*/*\" or \"type/*\" wildcards\n if (encoding.includes('*')) {\n return false\n }\n\n return encoding === mime\n }\n}\n\n/**\n * Creates a payload definition for Lexicon endpoint bodies.\n *\n * Defines the expected MIME type and optional validation schema for\n * request or response bodies.\n *\n * @param encoding - MIME type string (e.g., 'application/json', 'image/*'), or undefined for no body\n * @param validator - Optional schema for validating the body content. Must be undefined if encoding is undefined.\n * @returns A new {@link Payload} instance\n *\n * @example\n * ```ts\n * // JSON payload with schema\n * const output = l.payload('application/json', l.object({\n * posts: l.array(postSchema),\n * cursor: l.optional(l.string()),\n * }))\n *\n * // Binary payload (no schema validation)\n * const blobInput = l.payload('*\\/*', undefined)\n *\n * // Image payload with wildcard\n * const imageInput = l.payload('image/*', undefined)\n *\n * // No payload (for endpoints without body)\n * const noBody = l.payload()\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function payload<\n const E extends string | undefined = undefined,\n const S extends PayloadSchema<E> = undefined,\n>(encoding: E = undefined as E, validator: S = undefined as S) {\n return new Payload<E, S>(encoding, validator)\n}\n\n/**\n * Creates a JSON payload with an object schema.\n *\n * Convenience function for the common case of JSON request/response bodies.\n * Equivalent to `l.payload('application/json', l.object(properties))`.\n *\n * @param properties - Object mapping property names to validators\n * @returns A new {@link Payload} instance with 'application/json' encoding\n *\n * @example\n * ```ts\n * // Query output\n * const profileOutput = l.jsonPayload({\n * did: l.string({ format: 'did' }),\n * handle: l.string({ format: 'handle' }),\n * displayName: l.optional(l.string()),\n * })\n *\n * // Procedure input\n * const createPostInput = l.jsonPayload({\n * text: l.string({ maxGraphemes: 300 }),\n * createdAt: l.string({ format: 'datetime' }),\n * })\n * ```\n */\n/*@__NO_SIDE_EFFECTS__*/\nexport function jsonPayload<\n P extends Record<string, Validator<undefined | LexValue>>,\n>(properties: P): Payload<'application/json', ObjectSchema<P>> {\n return payload('application/json', object(properties))\n}\n"]}
|
package/dist/schema/string.js
CHANGED
|
@@ -74,7 +74,7 @@ class StringSchema extends core_js_1.Schema {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
const format = this.options.format;
|
|
77
|
-
if (format != null && !(0, core_js_1.isStringFormat)(str, format)) {
|
|
77
|
+
if (format != null && !(0, core_js_1.isStringFormat)(str, format, ctx.options)) {
|
|
78
78
|
return ctx.issueInvalidFormat(str, format);
|
|
79
79
|
}
|
|
80
80
|
return ctx.success(str);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"string.js","sourceRoot":"","sources":["../../src/schema/string.ts"],"names":[],"mappings":"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","sourcesContent":["import { graphemeLen, ifCid, utf8Len } from '@atproto/lex-data'\nimport {\n InferStringFormat,\n Restricted,\n Schema,\n StringFormat,\n UnknownString,\n ValidationContext,\n isStringFormat,\n} from '../core.js'\nimport { IfAny } from '../util/if-any.js'\nimport { memoizedOptions } from '../util/memoize.js'\nimport { TokenSchema } from './token.js'\n\n/**\n * Configuration options for string schema validation.\n *\n * @property format - Expected string format (e.g., 'datetime', 'uri', 'at-uri', 'did', 'handle', 'nsid', 'cid', 'tid', 'record-key', 'at-identifier', 'language')\n * @property knownValues - Known string literal values for type narrowing\n * @property minLength - Minimum length in UTF-8 bytes\n * @property maxLength - Maximum length in UTF-8 bytes\n * @property minGraphemes - Minimum number of grapheme clusters\n * @property maxGraphemes - Maximum number of grapheme clusters\n */\nexport type StringSchemaOptions = {\n format?: StringFormat\n knownValues?: readonly string[]\n minLength?: number\n maxLength?: number\n minGraphemes?: number\n maxGraphemes?: number\n}\n\n/**\n * Schema for validating string values with optional format and length constraints.\n *\n * Supports various string formats defined in the Lexicon specification, as well as\n * length constraints measured in UTF-8 bytes or grapheme clusters.\n *\n * @template TOptions - The configuration options type\n *\n * @example\n * ```ts\n * const schema = new StringSchema({ format: 'datetime', maxLength: 64 })\n * const result = schema.validate('2024-01-15T10:30:00Z')\n * ```\n */\nexport class StringSchema<\n const TOptions extends StringSchemaOptions = StringSchemaOptions,\n> extends Schema<\n IfAny<\n TOptions,\n string,\n TOptions extends { format: infer F extends StringFormat }\n ? InferStringFormat<F>\n : TOptions extends { knownValues: readonly (infer V extends string)[] }\n ? V | UnknownString\n : string\n >\n> {\n readonly type = 'string' as const\n\n // @NOTE since the _string utility allows omitting knownValues when TOptions\n // *does* include it (since it's only used for typing), we cannot type options\n // as TOptions directly since it may not actually include knownValues at\n // runtime, making schema.options.knownValues potentially undefined even when\n // TOptions includes it.\n readonly options: StringSchemaOptions\n\n constructor(options: TOptions) {\n super()\n this.options = options\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n const str = coerceToString(input)\n if (str == null) {\n return ctx.issueUnexpectedType(input, 'string')\n }\n\n let lazyUtf8Len: number\n\n const minLength = this.options.minLength\n if (minLength != null) {\n if ((lazyUtf8Len ??= utf8Len(str)) < minLength) {\n return ctx.issueTooSmall(str, 'string', minLength, lazyUtf8Len)\n }\n }\n\n const maxLength = this.options.maxLength\n if (maxLength != null) {\n // Optimization: we can avoid computing the UTF-8 length if the maximum\n // possible length, in bytes, of the input JS string is smaller than the\n // maxLength (in UTF-8 string bytes).\n if (str.length * 3 <= maxLength) {\n // Input string so small it can't possibly exceed maxLength\n } else if ((lazyUtf8Len ??= utf8Len(str)) > maxLength) {\n return ctx.issueTooBig(str, 'string', maxLength, lazyUtf8Len)\n }\n }\n\n let lazyGraphLen: number\n\n const minGraphemes = this.options.minGraphemes\n if (minGraphemes != null) {\n // Optimization: avoid counting graphemes if the length check already fails\n if (str.length < minGraphemes) {\n return ctx.issueTooSmall(str, 'grapheme', minGraphemes, str.length)\n } else if ((lazyGraphLen ??= graphemeLen(str)) < minGraphemes) {\n return ctx.issueTooSmall(str, 'grapheme', minGraphemes, lazyGraphLen)\n }\n }\n\n const maxGraphemes = this.options.maxGraphemes\n if (maxGraphemes != null) {\n if ((lazyGraphLen ??= graphemeLen(str)) > maxGraphemes) {\n return ctx.issueTooBig(str, 'grapheme', maxGraphemes, lazyGraphLen)\n }\n }\n\n const format = this.options.format\n if (format != null && !isStringFormat(str, format)) {\n return ctx.issueInvalidFormat(str, format)\n }\n\n return ctx.success(str)\n }\n}\n\nexport function coerceToString(input: unknown): string | null {\n switch (typeof input) {\n // @NOTE We do *not* coerce numbers/booleans to strings because that can\n // lead to them being accepted as string instead of being coerced to\n // number/boolean when the input is a string and the expected result is\n // number/boolean (e.g. in params).\n case 'string':\n return input\n case 'object': {\n if (input == null) return null\n\n // @NOTE Allow using TokenSchema instances in places expecting strings,\n // converting them to their string value.\n if (input instanceof TokenSchema) {\n return input.toString()\n }\n\n if (input instanceof Date) {\n if (Number.isNaN(input.getTime())) return null\n return input.toISOString()\n }\n\n if (input instanceof URL) {\n return input.toString()\n }\n\n const cid = ifCid(input)\n if (cid) return cid.toString()\n\n if (input instanceof String) {\n return input.valueOf()\n }\n }\n\n // falls through\n default:\n return null\n }\n}\n\nfunction _string(): StringSchema<NonNullable<unknown>>\nfunction _string<\n // Allow calling `string<{ knownValues: [...] }>()` without passing an options\n // object, since knownValues is only used for typing and has no runtime\n // effect, so it can be safely omitted at runtime.\n const TOptions extends {\n knownValues: StringSchemaOptions['knownValues']\n } & {\n [K in Exclude<\n keyof StringSchemaOptions,\n 'knownValues'\n >]?: Restricted<`An options argument is required when using the \"${K}\" option`>\n },\n>(): StringSchema<\n IfAny<TOptions, any, { knownValues: TOptions['knownValues'] }>\n>\nfunction _string<const TOptions extends StringSchemaOptions>(\n // If TOptions is explicitly provided (e.g. `string<{ ... }>({ ... })`), we\n // allow the actual options argument to omit the \"knownValues\" property since\n // it's only used for inferring the type and has no runtime effect.\n options: TOptions | Omit<TOptions, 'knownValues'>,\n): StringSchema<TOptions>\nfunction _string(options: StringSchemaOptions = {}) {\n return new StringSchema(options)\n}\n\n/**\n * Creates a string schema with optional format and length constraints.\n *\n * Strings can be validated against various formats (datetime, uri, did, handle, etc.)\n * and constrained by length in UTF-8 bytes or grapheme clusters.\n *\n * @param options - Optional configuration for format and length constraints\n * @returns A new {@link StringSchema} instance\n *\n * @example\n * ```ts\n * // Basic string\n * const nameSchema = l.string()\n *\n * // With format validation\n * const dateSchema = l.string({ format: 'datetime' })\n *\n * // With length constraints (UTF-8 bytes)\n * const bioSchema = l.string({ maxLength: 256 })\n *\n * // With grapheme constraints (user-perceived characters)\n * const displayNameSchema = l.string({ maxGraphemes: 64 })\n *\n * // Combining constraints\n * const handleSchema = l.string({ format: 'handle', minLength: 3, maxLength: 253 })\n * ```\n */\nexport const string = /*#__PURE__*/ memoizedOptions(_string)\n"]}
|
|
1
|
+
{"version":3,"file":"string.js","sourceRoot":"","sources":["../../src/schema/string.ts"],"names":[],"mappings":"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","sourcesContent":["import { graphemeLen, ifCid, utf8Len } from '@atproto/lex-data'\nimport {\n InferStringFormat,\n Restricted,\n Schema,\n StringFormat,\n UnknownString,\n ValidationContext,\n isStringFormat,\n} from '../core.js'\nimport { IfAny } from '../util/if-any.js'\nimport { memoizedOptions } from '../util/memoize.js'\nimport { TokenSchema } from './token.js'\n\n/**\n * Configuration options for string schema validation.\n *\n * @property format - Expected string format (e.g., 'datetime', 'uri', 'at-uri', 'did', 'handle', 'nsid', 'cid', 'tid', 'record-key', 'at-identifier', 'language')\n * @property knownValues - Known string literal values for type narrowing\n * @property minLength - Minimum length in UTF-8 bytes\n * @property maxLength - Maximum length in UTF-8 bytes\n * @property minGraphemes - Minimum number of grapheme clusters\n * @property maxGraphemes - Maximum number of grapheme clusters\n */\nexport type StringSchemaOptions = {\n format?: StringFormat\n knownValues?: readonly string[]\n minLength?: number\n maxLength?: number\n minGraphemes?: number\n maxGraphemes?: number\n}\n\n/**\n * Schema for validating string values with optional format and length constraints.\n *\n * Supports various string formats defined in the Lexicon specification, as well as\n * length constraints measured in UTF-8 bytes or grapheme clusters.\n *\n * @template TOptions - The configuration options type\n *\n * @example\n * ```ts\n * const schema = new StringSchema({ format: 'datetime', maxLength: 64 })\n * const result = schema.validate('2024-01-15T10:30:00Z')\n * ```\n */\nexport class StringSchema<\n const TOptions extends StringSchemaOptions = StringSchemaOptions,\n> extends Schema<\n IfAny<\n TOptions,\n string,\n TOptions extends { format: infer F extends StringFormat }\n ? InferStringFormat<F>\n : TOptions extends { knownValues: readonly (infer V extends string)[] }\n ? V | UnknownString\n : string\n >\n> {\n readonly type = 'string' as const\n\n // @NOTE since the _string utility allows omitting knownValues when TOptions\n // *does* include it (since it's only used for typing), we cannot type options\n // as TOptions directly since it may not actually include knownValues at\n // runtime, making schema.options.knownValues potentially undefined even when\n // TOptions includes it.\n readonly options: StringSchemaOptions\n\n constructor(options: TOptions) {\n super()\n this.options = options\n }\n\n validateInContext(input: unknown, ctx: ValidationContext) {\n const str = coerceToString(input)\n if (str == null) {\n return ctx.issueUnexpectedType(input, 'string')\n }\n\n let lazyUtf8Len: number\n\n const minLength = this.options.minLength\n if (minLength != null) {\n if ((lazyUtf8Len ??= utf8Len(str)) < minLength) {\n return ctx.issueTooSmall(str, 'string', minLength, lazyUtf8Len)\n }\n }\n\n const maxLength = this.options.maxLength\n if (maxLength != null) {\n // Optimization: we can avoid computing the UTF-8 length if the maximum\n // possible length, in bytes, of the input JS string is smaller than the\n // maxLength (in UTF-8 string bytes).\n if (str.length * 3 <= maxLength) {\n // Input string so small it can't possibly exceed maxLength\n } else if ((lazyUtf8Len ??= utf8Len(str)) > maxLength) {\n return ctx.issueTooBig(str, 'string', maxLength, lazyUtf8Len)\n }\n }\n\n let lazyGraphLen: number\n\n const minGraphemes = this.options.minGraphemes\n if (minGraphemes != null) {\n // Optimization: avoid counting graphemes if the length check already fails\n if (str.length < minGraphemes) {\n return ctx.issueTooSmall(str, 'grapheme', minGraphemes, str.length)\n } else if ((lazyGraphLen ??= graphemeLen(str)) < minGraphemes) {\n return ctx.issueTooSmall(str, 'grapheme', minGraphemes, lazyGraphLen)\n }\n }\n\n const maxGraphemes = this.options.maxGraphemes\n if (maxGraphemes != null) {\n if ((lazyGraphLen ??= graphemeLen(str)) > maxGraphemes) {\n return ctx.issueTooBig(str, 'grapheme', maxGraphemes, lazyGraphLen)\n }\n }\n\n const format = this.options.format\n if (format != null && !isStringFormat(str, format, ctx.options)) {\n return ctx.issueInvalidFormat(str, format)\n }\n\n return ctx.success(str)\n }\n}\n\nexport function coerceToString(input: unknown): string | null {\n switch (typeof input) {\n // @NOTE We do *not* coerce numbers/booleans to strings because that can\n // lead to them being accepted as string instead of being coerced to\n // number/boolean when the input is a string and the expected result is\n // number/boolean (e.g. in params).\n case 'string':\n return input\n case 'object': {\n if (input == null) return null\n\n // @NOTE Allow using TokenSchema instances in places expecting strings,\n // converting them to their string value.\n if (input instanceof TokenSchema) {\n return input.toString()\n }\n\n if (input instanceof Date) {\n if (Number.isNaN(input.getTime())) return null\n return input.toISOString()\n }\n\n if (input instanceof URL) {\n return input.toString()\n }\n\n const cid = ifCid(input)\n if (cid) return cid.toString()\n\n if (input instanceof String) {\n return input.valueOf()\n }\n }\n\n // falls through\n default:\n return null\n }\n}\n\nfunction _string(): StringSchema<NonNullable<unknown>>\nfunction _string<\n // Allow calling `string<{ knownValues: [...] }>()` without passing an options\n // object, since knownValues is only used for typing and has no runtime\n // effect, so it can be safely omitted at runtime.\n const TOptions extends {\n knownValues: StringSchemaOptions['knownValues']\n } & {\n [K in Exclude<\n keyof StringSchemaOptions,\n 'knownValues'\n >]?: Restricted<`An options argument is required when using the \"${K}\" option`>\n },\n>(): StringSchema<\n IfAny<TOptions, any, { knownValues: TOptions['knownValues'] }>\n>\nfunction _string<const TOptions extends StringSchemaOptions>(\n // If TOptions is explicitly provided (e.g. `string<{ ... }>({ ... })`), we\n // allow the actual options argument to omit the \"knownValues\" property since\n // it's only used for inferring the type and has no runtime effect.\n options: TOptions | Omit<TOptions, 'knownValues'>,\n): StringSchema<TOptions>\nfunction _string(options: StringSchemaOptions = {}) {\n return new StringSchema(options)\n}\n\n/**\n * Creates a string schema with optional format and length constraints.\n *\n * Strings can be validated against various formats (datetime, uri, did, handle, etc.)\n * and constrained by length in UTF-8 bytes or grapheme clusters.\n *\n * @param options - Optional configuration for format and length constraints\n * @returns A new {@link StringSchema} instance\n *\n * @example\n * ```ts\n * // Basic string\n * const nameSchema = l.string()\n *\n * // With format validation\n * const dateSchema = l.string({ format: 'datetime' })\n *\n * // With length constraints (UTF-8 bytes)\n * const bioSchema = l.string({ maxLength: 256 })\n *\n * // With grapheme constraints (user-perceived characters)\n * const displayNameSchema = l.string({ maxGraphemes: 64 })\n *\n * // Combining constraints\n * const handleSchema = l.string({ format: 'handle', minLength: 3, maxLength: 253 })\n * ```\n */\nexport const string = /*#__PURE__*/ memoizedOptions(_string)\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/lex-schema",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.16",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Lexicon schema system for AT Lexicons",
|
|
6
6
|
"keywords": [
|
|
@@ -36,9 +36,10 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@standard-schema/spec": "^1.1.0",
|
|
39
|
+
"iso-datestring-validator": "^2.2.2",
|
|
39
40
|
"tslib": "^2.8.1",
|
|
40
41
|
"@atproto/syntax": "^0.5.1",
|
|
41
|
-
"@atproto/lex-data": "^0.0.
|
|
42
|
+
"@atproto/lex-data": "^0.0.14"
|
|
42
43
|
},
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"vitest": "^4.0.16"
|
package/src/core/schema.ts
CHANGED
|
@@ -47,7 +47,7 @@ export interface SchemaInternals<out TInput = unknown, out TOutput = TInput> {
|
|
|
47
47
|
* - **Assertion methods**: `assert()`, `check()` - throw on invalid input
|
|
48
48
|
* - **Type guard methods**: `matches()`, `ifMatches()` - return boolean or optional value
|
|
49
49
|
* - **Parse methods**: `parse()`, `safeParse()` - allow value transformation/coercion
|
|
50
|
-
* - **Validate methods**: `validate()`, `safeValidate()` -
|
|
50
|
+
* - **Validate methods**: `validate()`, `safeValidate()` - validation without coercion
|
|
51
51
|
*
|
|
52
52
|
* All methods are also available with a `$` prefix (e.g., `$parse()`, `$validate()`)
|
|
53
53
|
* for consistent access in generated lexicon namespaces.
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isValidISODateString } from 'iso-datestring-validator'
|
|
1
2
|
import { validateCidString } from '@atproto/lex-data'
|
|
2
3
|
import {
|
|
3
4
|
AtIdentifierString,
|
|
@@ -48,6 +49,26 @@ export {
|
|
|
48
49
|
isDatetimeString,
|
|
49
50
|
} from '@atproto/syntax'
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Matches any ISO-ish datetime string. This is a more lenient check than
|
|
54
|
+
* the strict {@link isDatetimeString} guard, which only allows datetimes that
|
|
55
|
+
* fully conform to the AT Protocol specification (e.g. must include timezone).
|
|
56
|
+
*/
|
|
57
|
+
export function isDatetimeStringLoose<I>(
|
|
58
|
+
input: I,
|
|
59
|
+
): input is I & DatetimeString {
|
|
60
|
+
// @NOTE the returned type assertion is inaccurate wrt. the DatetimeString
|
|
61
|
+
// type definition. A more accurate solution would be to use a branded type
|
|
62
|
+
// instead of a template literal for the "datetime" format
|
|
63
|
+
if (typeof input !== 'string') return false
|
|
64
|
+
try {
|
|
65
|
+
return isValidISODateString(input)
|
|
66
|
+
} catch {
|
|
67
|
+
// @NOTE isValidISODateString throws on some inputs
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
51
72
|
// DatetimeString utilities
|
|
52
73
|
export { currentDatetimeString, toDatetimeString } from '@atproto/syntax'
|
|
53
74
|
|
|
@@ -223,23 +244,39 @@ type StringFormats = {
|
|
|
223
244
|
export type StringFormat = Extract<keyof StringFormats, string>
|
|
224
245
|
|
|
225
246
|
const stringFormatVerifiers: {
|
|
226
|
-
readonly [K in StringFormat]:
|
|
247
|
+
readonly [K in StringFormat]: readonly [
|
|
248
|
+
strict: CheckFn<StringFormats[K]>,
|
|
249
|
+
loose?: CheckFn<StringFormats[K]>,
|
|
250
|
+
]
|
|
227
251
|
} = /*#__PURE__*/ Object.freeze({
|
|
228
252
|
__proto__: null,
|
|
229
253
|
|
|
230
|
-
'at-identifier': isAtIdentifierString,
|
|
231
|
-
'at-uri': isAtUriString,
|
|
232
|
-
cid: isCidString,
|
|
233
|
-
datetime: isDatetimeString,
|
|
234
|
-
did: isDidString,
|
|
235
|
-
handle: isHandleString,
|
|
236
|
-
language: isLanguageString,
|
|
237
|
-
nsid: isNsidString,
|
|
238
|
-
'record-key': isRecordKeyString,
|
|
239
|
-
tid: isTidString,
|
|
240
|
-
uri: isUriString,
|
|
254
|
+
'at-identifier': [isAtIdentifierString],
|
|
255
|
+
'at-uri': [isAtUriString],
|
|
256
|
+
cid: [isCidString],
|
|
257
|
+
datetime: [isDatetimeString, isDatetimeStringLoose],
|
|
258
|
+
did: [isDidString],
|
|
259
|
+
handle: [isHandleString],
|
|
260
|
+
language: [isLanguageString],
|
|
261
|
+
nsid: [isNsidString],
|
|
262
|
+
'record-key': [isRecordKeyString],
|
|
263
|
+
tid: [isTidString],
|
|
264
|
+
uri: [isUriString],
|
|
241
265
|
})
|
|
242
266
|
|
|
267
|
+
export type StringFormatValidationOptions = {
|
|
268
|
+
/**
|
|
269
|
+
* Allows to be more lenient in validation by using a "loose" verification
|
|
270
|
+
* function, if available. The behavior of the loose verifier depends on the
|
|
271
|
+
* specific format, but generally it may allow for a wider range of valid
|
|
272
|
+
* inputs, including values that are not compliant with the AT Protocol
|
|
273
|
+
* specification.
|
|
274
|
+
*
|
|
275
|
+
* @default true
|
|
276
|
+
*/
|
|
277
|
+
strict?: boolean
|
|
278
|
+
}
|
|
279
|
+
|
|
243
280
|
/**
|
|
244
281
|
* Infers the string type for a given format name.
|
|
245
282
|
*
|
|
@@ -277,12 +314,18 @@ export type InferStringFormat<F extends StringFormat> = F extends StringFormat
|
|
|
277
314
|
export function isStringFormat<I extends string, F extends StringFormat>(
|
|
278
315
|
input: I,
|
|
279
316
|
format: F,
|
|
317
|
+
options?: StringFormatValidationOptions,
|
|
280
318
|
): input is I & StringFormats[F] {
|
|
281
319
|
const formatVerifier = stringFormatVerifiers[format]
|
|
282
320
|
// Fool-proof
|
|
283
321
|
if (!formatVerifier) throw new TypeError(`Unknown string format: ${format}`)
|
|
284
322
|
|
|
285
|
-
|
|
323
|
+
const check: CheckFn<StringFormats[F]> =
|
|
324
|
+
options?.strict === false
|
|
325
|
+
? formatVerifier[1] ?? formatVerifier[0]
|
|
326
|
+
: formatVerifier[0]
|
|
327
|
+
|
|
328
|
+
return check(input)
|
|
286
329
|
}
|
|
287
330
|
|
|
288
331
|
/**
|
|
@@ -304,8 +347,9 @@ export function isStringFormat<I extends string, F extends StringFormat>(
|
|
|
304
347
|
export function assertStringFormat<I extends string, F extends StringFormat>(
|
|
305
348
|
input: I,
|
|
306
349
|
format: F,
|
|
350
|
+
options?: StringFormatValidationOptions,
|
|
307
351
|
): asserts input is I & StringFormats[F] {
|
|
308
|
-
if (!isStringFormat(input, format)) {
|
|
352
|
+
if (!isStringFormat(input, format, options)) {
|
|
309
353
|
throw new TypeError(`Invalid string format (${format}): ${input}`)
|
|
310
354
|
}
|
|
311
355
|
}
|
|
@@ -332,8 +376,9 @@ export function assertStringFormat<I extends string, F extends StringFormat>(
|
|
|
332
376
|
export function asStringFormat<I extends string, F extends StringFormat>(
|
|
333
377
|
input: I,
|
|
334
378
|
format: F,
|
|
379
|
+
options?: StringFormatValidationOptions,
|
|
335
380
|
): I & StringFormats[F] {
|
|
336
|
-
assertStringFormat(input, format)
|
|
381
|
+
assertStringFormat(input, format, options)
|
|
337
382
|
return input
|
|
338
383
|
}
|
|
339
384
|
|
|
@@ -361,8 +406,9 @@ export function asStringFormat<I extends string, F extends StringFormat>(
|
|
|
361
406
|
export function ifStringFormat<I extends string, F extends StringFormat>(
|
|
362
407
|
input: I,
|
|
363
408
|
format: F,
|
|
409
|
+
options?: StringFormatValidationOptions,
|
|
364
410
|
): undefined | (I & StringFormats[F]) {
|
|
365
|
-
return isStringFormat(input, format) ? input : undefined
|
|
411
|
+
return isStringFormat(input, format, options) ? input : undefined
|
|
366
412
|
}
|
|
367
413
|
|
|
368
414
|
/**
|
package/src/core/validator.ts
CHANGED
|
@@ -152,11 +152,13 @@ export type ValidationOptions = {
|
|
|
152
152
|
/**
|
|
153
153
|
* The validation mode determining how transformations are handled.
|
|
154
154
|
*
|
|
155
|
-
* - `"validate"
|
|
155
|
+
* - `"validate"`: Strict validation where the result must be
|
|
156
156
|
* strictly equal to the input value. No transformations such as applying
|
|
157
157
|
* default values are allowed.
|
|
158
158
|
* - `"parse"`: Allows the schema to transform the input value, such as
|
|
159
159
|
* applying default values or performing type coercion.
|
|
160
|
+
*
|
|
161
|
+
* @default "validate"
|
|
160
162
|
*/
|
|
161
163
|
mode?: 'validate' | 'parse'
|
|
162
164
|
|
|
@@ -173,6 +175,17 @@ export type ValidationOptions = {
|
|
|
173
175
|
* ```
|
|
174
176
|
*/
|
|
175
177
|
path?: readonly PropertyKey[]
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Whether to enforce strict validation rules (e.g., MIME type matching, size
|
|
181
|
+
* limits, datetime format).
|
|
182
|
+
*
|
|
183
|
+
* This is typically useful to allow more lax validation when parsing server
|
|
184
|
+
* responses, while enforcing strict validation for user input.
|
|
185
|
+
*
|
|
186
|
+
* @default true
|
|
187
|
+
*/
|
|
188
|
+
strict?: boolean
|
|
176
189
|
}
|
|
177
190
|
|
|
178
191
|
/**
|
|
@@ -221,6 +234,7 @@ export class ValidationContext {
|
|
|
221
234
|
mode: 'parse'
|
|
222
235
|
},
|
|
223
236
|
): ValidationResult<InferOutput<V>>
|
|
237
|
+
|
|
224
238
|
/**
|
|
225
239
|
* Validates input against a validator in validate mode (default).
|
|
226
240
|
*
|
|
@@ -241,6 +255,7 @@ export class ValidationContext {
|
|
|
241
255
|
mode?: 'validate'
|
|
242
256
|
},
|
|
243
257
|
): ValidationResult<I & InferInput<V>>
|
|
258
|
+
|
|
244
259
|
/**
|
|
245
260
|
* Validates input against a validator with configurable options.
|
|
246
261
|
*
|
|
@@ -262,6 +277,7 @@ export class ValidationContext {
|
|
|
262
277
|
const context = new ValidationContext({
|
|
263
278
|
path: options?.path ?? [],
|
|
264
279
|
mode: options?.mode ?? 'validate',
|
|
280
|
+
strict: options?.strict ?? true,
|
|
265
281
|
})
|
|
266
282
|
return context.validate(input, validator)
|
|
267
283
|
}
|