prototypey.org - atproto lexicon typescript toolkit - mirror https://github.com/tylersayshi/prototypey
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add enum & knownValues type hints (#93)

* Add enum & knownValues type hints

Adds type hints for enum values & knownValues on strings.

* Add changeset for enum & knownValues hints

* Add const type param to lx.string for automatic literal inference

Callers no longer need `as const` on enum/knownValues arrays to get
type-level autocomplete suggestions. Adds snapshot tests for both features.

* Fix formatting

authored by

JP Hastings-Edrei and committed by
GitHub
7b680c9b 645ddc6c

+121 -2
+7
.changeset/enum-known-values-type-hints.md
··· 1 + --- 2 + "prototypey": patch 3 + --- 4 + 5 + Add type-level inference for `enum` and `knownValues` on string fields. 6 + 7 + Strings with `enum` now infer as a union of the literal values; strings with `knownValues` infer as the literal union widened with `string & {}` so unlisted values are still accepted.
+10 -1
packages/prototypey/core/infer.ts
··· 26 26 : T extends { type: "integer" } 27 27 ? number 28 28 : T extends { type: "string" } 29 - ? string 29 + ? T extends { 30 + enum: readonly (infer E extends string)[]; 31 + } 32 + ? E 33 + : T extends { 34 + knownValues: readonly (infer K extends 35 + string)[]; 36 + } 37 + ? K | (string & {}) 38 + : string 30 39 : T extends { type: "bytes" } 31 40 ? Uint8Array 32 41 : T extends { type: "cid-link" }
+1 -1
packages/prototypey/core/lib.ts
··· 578 578 * Creates a string type with optional format, length, and value constraints. 579 579 * @see https://atproto.com/specs/lexicon#string 580 580 */ 581 - string<T extends StringOptions>(options?: T): T & { type: "string" } { 581 + string<const T extends StringOptions>(options?: T): T & { type: "string" } { 582 582 return { 583 583 type: "string", 584 584 ...options,
+103
packages/prototypey/core/tests/infer.test.ts
··· 69 69 }`); 70 70 }); 71 71 72 + test("InferType handles string with enum constraint", () => { 73 + const lexicon = lx.lexicon("test.stringEnum", { 74 + main: lx.object({ 75 + status: lx.string({ enum: ["active", "inactive", "pending"] }), 76 + }), 77 + }); 78 + 79 + attest(lexicon["~infer"]).type.toString.snap(`{ 80 + $type: "test.stringEnum" 81 + status?: "active" | "inactive" | "pending" | undefined 82 + }`); 83 + }); 84 + 85 + test("InferType handles string with knownValues hint", () => { 86 + const lexicon = lx.lexicon("test.stringKnownValues", { 87 + main: lx.object({ 88 + lang: lx.string({ 89 + knownValues: ["en", "fr", "de"], 90 + }), 91 + }), 92 + }); 93 + 94 + // attest expands all of `strings` methods here, which we need 95 + // because atproto's `knownValues` are an open set. 96 + attest(lexicon["~infer"]).type.toString.snap(`{ 97 + $type: "test.stringKnownValues" 98 + lang?: 99 + | "en" 100 + | "fr" 101 + | "de" 102 + | { 103 + readonly [x: number]: string 104 + toString: () => string 105 + charAt: {} 106 + charCodeAt: {} 107 + concat: {} 108 + indexOf: {} 109 + lastIndexOf: {} 110 + localeCompare: {} 111 + match: {} 112 + replace: {} 113 + search: {} 114 + slice: {} 115 + split: {} 116 + substring: {} 117 + toLowerCase: () => string 118 + toLocaleLowerCase: {} 119 + toUpperCase: () => string 120 + toLocaleUpperCase: {} 121 + trim: () => string 122 + readonly length: number 123 + substr: {} 124 + valueOf: () => string 125 + codePointAt: {} 126 + includes: {} 127 + endsWith: {} 128 + normalize: {} 129 + repeat: {} 130 + startsWith: {} 131 + anchor: {} 132 + big: () => string 133 + blink: () => string 134 + bold: () => string 135 + fixed: () => string 136 + fontcolor: {} 137 + fontsize: {} 138 + italics: () => string 139 + link: {} 140 + small: () => string 141 + strike: () => string 142 + sub: () => string 143 + sup: () => string 144 + padStart: {} 145 + padEnd: {} 146 + trimEnd: () => string 147 + trimStart: () => string 148 + trimLeft: () => string 149 + trimRight: () => string 150 + matchAll: {} 151 + replaceAll: {} 152 + at: {} 153 + [Symbol.iterator]: () => StringIterator<string> 154 + } 155 + | undefined 156 + }`); 157 + }); 158 + 159 + test("InferType handles required string with enum", () => { 160 + const lexicon = lx.lexicon("test.requiredEnum", { 161 + main: lx.object({ 162 + role: lx.string({ 163 + required: true, 164 + enum: ["admin", "user", "guest"], 165 + }), 166 + }), 167 + }); 168 + 169 + attest(lexicon["~infer"]).type.toString.snap(`{ 170 + $type: "test.requiredEnum" 171 + role: "user" | "admin" | "guest" 172 + }`); 173 + }); 174 + 72 175 test("InferType handles integer primitive", () => { 73 176 const lexicon = lx.lexicon("test.integer", { 74 177 main: lx.object({