@atproto/lex-schema 0.0.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 (243) hide show
  1. package/dist/core/$type.d.ts +4 -0
  2. package/dist/core/$type.d.ts.map +1 -0
  3. package/dist/core/$type.js +7 -0
  4. package/dist/core/$type.js.map +1 -0
  5. package/dist/core/record-key.d.ts +4 -0
  6. package/dist/core/record-key.d.ts.map +1 -0
  7. package/dist/core/record-key.js +16 -0
  8. package/dist/core/record-key.js.map +1 -0
  9. package/dist/core/result.d.ts +57 -0
  10. package/dist/core/result.d.ts.map +1 -0
  11. package/dist/core/result.js +74 -0
  12. package/dist/core/result.js.map +1 -0
  13. package/dist/core/string-format.d.ts +31 -0
  14. package/dist/core/string-format.d.ts.map +1 -0
  15. package/dist/core/string-format.js +81 -0
  16. package/dist/core/string-format.js.map +1 -0
  17. package/dist/core/types.d.ts +19 -0
  18. package/dist/core/types.d.ts.map +1 -0
  19. package/dist/core/types.js +3 -0
  20. package/dist/core/types.js.map +1 -0
  21. package/dist/core.d.ts +6 -0
  22. package/dist/core.d.ts.map +1 -0
  23. package/dist/core.js +9 -0
  24. package/dist/core.js.map +1 -0
  25. package/dist/external.d.ts +86 -0
  26. package/dist/external.d.ts.map +1 -0
  27. package/dist/external.js +171 -0
  28. package/dist/external.js.map +1 -0
  29. package/dist/index.d.ts +4 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +8 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/schema/_parameters.d.ts +17 -0
  34. package/dist/schema/_parameters.d.ts.map +1 -0
  35. package/dist/schema/_parameters.js +20 -0
  36. package/dist/schema/_parameters.js.map +1 -0
  37. package/dist/schema/array.d.ts +13 -0
  38. package/dist/schema/array.d.ts.map +1 -0
  39. package/dist/schema/array.js +40 -0
  40. package/dist/schema/array.js.map +1 -0
  41. package/dist/schema/blob.d.ts +32 -0
  42. package/dist/schema/blob.d.ts.map +1 -0
  43. package/dist/schema/blob.js +40 -0
  44. package/dist/schema/blob.js.map +1 -0
  45. package/dist/schema/boolean.d.ts +11 -0
  46. package/dist/schema/boolean.d.ts.map +1 -0
  47. package/dist/schema/boolean.js +20 -0
  48. package/dist/schema/boolean.js.map +1 -0
  49. package/dist/schema/bytes.d.ts +12 -0
  50. package/dist/schema/bytes.d.ts.map +1 -0
  51. package/dist/schema/bytes.js +31 -0
  52. package/dist/schema/bytes.js.map +1 -0
  53. package/dist/schema/cid.d.ts +13 -0
  54. package/dist/schema/cid.d.ts.map +1 -0
  55. package/dist/schema/cid.js +22 -0
  56. package/dist/schema/cid.js.map +1 -0
  57. package/dist/schema/custom.d.ts +15 -0
  58. package/dist/schema/custom.d.ts.map +1 -0
  59. package/dist/schema/custom.js +22 -0
  60. package/dist/schema/custom.js.map +1 -0
  61. package/dist/schema/dict.d.ts +18 -0
  62. package/dist/schema/dict.d.ts.map +1 -0
  63. package/dist/schema/dict.js +48 -0
  64. package/dist/schema/dict.js.map +1 -0
  65. package/dist/schema/discriminated-union.d.ts +34 -0
  66. package/dist/schema/discriminated-union.d.ts.map +1 -0
  67. package/dist/schema/discriminated-union.js +93 -0
  68. package/dist/schema/discriminated-union.js.map +1 -0
  69. package/dist/schema/enum.d.ts +7 -0
  70. package/dist/schema/enum.d.ts.map +1 -0
  71. package/dist/schema/enum.js +19 -0
  72. package/dist/schema/enum.js.map +1 -0
  73. package/dist/schema/integer.d.ts +13 -0
  74. package/dist/schema/integer.d.ts.map +1 -0
  75. package/dist/schema/integer.js +32 -0
  76. package/dist/schema/integer.js.map +1 -0
  77. package/dist/schema/intersection.d.ts +16 -0
  78. package/dist/schema/intersection.d.ts.map +1 -0
  79. package/dist/schema/intersection.js +33 -0
  80. package/dist/schema/intersection.js.map +1 -0
  81. package/dist/schema/literal.d.ts +7 -0
  82. package/dist/schema/literal.d.ts.map +1 -0
  83. package/dist/schema/literal.js +19 -0
  84. package/dist/schema/literal.js.map +1 -0
  85. package/dist/schema/never.d.ts +5 -0
  86. package/dist/schema/never.d.ts.map +1 -0
  87. package/dist/schema/never.js +11 -0
  88. package/dist/schema/never.js.map +1 -0
  89. package/dist/schema/null.d.ts +7 -0
  90. package/dist/schema/null.d.ts.map +1 -0
  91. package/dist/schema/null.js +18 -0
  92. package/dist/schema/null.js.map +1 -0
  93. package/dist/schema/object.d.ts +47 -0
  94. package/dist/schema/object.d.ts.map +1 -0
  95. package/dist/schema/object.js +89 -0
  96. package/dist/schema/object.js.map +1 -0
  97. package/dist/schema/params.d.ts +22 -0
  98. package/dist/schema/params.d.ts.map +1 -0
  99. package/dist/schema/params.js +115 -0
  100. package/dist/schema/params.js.map +1 -0
  101. package/dist/schema/payload.d.ts +19 -0
  102. package/dist/schema/payload.d.ts.map +1 -0
  103. package/dist/schema/payload.js +16 -0
  104. package/dist/schema/payload.js.map +1 -0
  105. package/dist/schema/permission-set.d.ts +15 -0
  106. package/dist/schema/permission-set.d.ts.map +1 -0
  107. package/dist/schema/permission-set.js +16 -0
  108. package/dist/schema/permission-set.js.map +1 -0
  109. package/dist/schema/permission.d.ts +9 -0
  110. package/dist/schema/permission.d.ts.map +1 -0
  111. package/dist/schema/permission.js +14 -0
  112. package/dist/schema/permission.js.map +1 -0
  113. package/dist/schema/procedure.d.ts +17 -0
  114. package/dist/schema/procedure.d.ts.map +1 -0
  115. package/dist/schema/procedure.js +20 -0
  116. package/dist/schema/procedure.js.map +1 -0
  117. package/dist/schema/query.d.ts +15 -0
  118. package/dist/schema/query.d.ts.map +1 -0
  119. package/dist/schema/query.js +18 -0
  120. package/dist/schema/query.js.map +1 -0
  121. package/dist/schema/record.d.ts +37 -0
  122. package/dist/schema/record.d.ts.map +1 -0
  123. package/dist/schema/record.js +64 -0
  124. package/dist/schema/record.js.map +1 -0
  125. package/dist/schema/ref.d.ts +10 -0
  126. package/dist/schema/ref.d.ts.map +1 -0
  127. package/dist/schema/ref.js +36 -0
  128. package/dist/schema/ref.js.map +1 -0
  129. package/dist/schema/string.d.ts +24 -0
  130. package/dist/schema/string.d.ts.map +1 -0
  131. package/dist/schema/string.js +107 -0
  132. package/dist/schema/string.js.map +1 -0
  133. package/dist/schema/subscription.d.ts +16 -0
  134. package/dist/schema/subscription.d.ts.map +1 -0
  135. package/dist/schema/subscription.js +18 -0
  136. package/dist/schema/subscription.js.map +1 -0
  137. package/dist/schema/token.d.ts +10 -0
  138. package/dist/schema/token.d.ts.map +1 -0
  139. package/dist/schema/token.js +36 -0
  140. package/dist/schema/token.js.map +1 -0
  141. package/dist/schema/typed-object.d.ts +32 -0
  142. package/dist/schema/typed-object.d.ts.map +1 -0
  143. package/dist/schema/typed-object.js +40 -0
  144. package/dist/schema/typed-object.js.map +1 -0
  145. package/dist/schema/typed-ref.d.ts +30 -0
  146. package/dist/schema/typed-ref.d.ts.map +1 -0
  147. package/dist/schema/typed-ref.js +44 -0
  148. package/dist/schema/typed-ref.js.map +1 -0
  149. package/dist/schema/typed-union.d.ts +26 -0
  150. package/dist/schema/typed-union.d.ts.map +1 -0
  151. package/dist/schema/typed-union.js +54 -0
  152. package/dist/schema/typed-union.js.map +1 -0
  153. package/dist/schema/union.d.ts +9 -0
  154. package/dist/schema/union.d.ts.map +1 -0
  155. package/dist/schema/union.js +29 -0
  156. package/dist/schema/union.js.map +1 -0
  157. package/dist/schema/unknown-object.d.ts +9 -0
  158. package/dist/schema/unknown-object.d.ts.map +1 -0
  159. package/dist/schema/unknown-object.js +16 -0
  160. package/dist/schema/unknown-object.js.map +1 -0
  161. package/dist/schema/unknown.d.ts +5 -0
  162. package/dist/schema/unknown.d.ts.map +1 -0
  163. package/dist/schema/unknown.js +11 -0
  164. package/dist/schema/unknown.js.map +1 -0
  165. package/dist/schema.d.ts +34 -0
  166. package/dist/schema.d.ts.map +1 -0
  167. package/dist/schema.js +41 -0
  168. package/dist/schema.js.map +1 -0
  169. package/dist/util/array-agg.d.ts +20 -0
  170. package/dist/util/array-agg.d.ts.map +1 -0
  171. package/dist/util/array-agg.js +42 -0
  172. package/dist/util/array-agg.js.map +1 -0
  173. package/dist/validation/property-key.d.ts +2 -0
  174. package/dist/validation/property-key.d.ts.map +1 -0
  175. package/dist/validation/property-key.js +3 -0
  176. package/dist/validation/property-key.js.map +1 -0
  177. package/dist/validation/validation-error.d.ts +9 -0
  178. package/dist/validation/validation-error.d.ts.map +1 -0
  179. package/dist/validation/validation-error.js +27 -0
  180. package/dist/validation/validation-error.js.map +1 -0
  181. package/dist/validation/validation-issue.d.ts +45 -0
  182. package/dist/validation/validation-issue.d.ts.map +1 -0
  183. package/dist/validation/validation-issue.js +167 -0
  184. package/dist/validation/validation-issue.js.map +1 -0
  185. package/dist/validation/validator.d.ts +113 -0
  186. package/dist/validation/validator.d.ts.map +1 -0
  187. package/dist/validation/validator.js +209 -0
  188. package/dist/validation/validator.js.map +1 -0
  189. package/dist/validation.d.ts +5 -0
  190. package/dist/validation.d.ts.map +1 -0
  191. package/dist/validation.js +8 -0
  192. package/dist/validation.js.map +1 -0
  193. package/package.json +45 -0
  194. package/src/core/$type.ts +19 -0
  195. package/src/core/record-key.ts +15 -0
  196. package/src/core/result.ts +73 -0
  197. package/src/core/string-format.ts +124 -0
  198. package/src/core/types.ts +22 -0
  199. package/src/core.ts +5 -0
  200. package/src/external.ts +365 -0
  201. package/src/index.ts +3 -0
  202. package/src/schema/_parameters.ts +26 -0
  203. package/src/schema/array.ts +51 -0
  204. package/src/schema/blob.ts +82 -0
  205. package/src/schema/boolean.ts +24 -0
  206. package/src/schema/bytes.ts +38 -0
  207. package/src/schema/cid.ts +27 -0
  208. package/src/schema/custom.ts +36 -0
  209. package/src/schema/dict.ts +69 -0
  210. package/src/schema/discriminated-union.ts +144 -0
  211. package/src/schema/enum.ts +20 -0
  212. package/src/schema/integer.ts +41 -0
  213. package/src/schema/intersection.ts +57 -0
  214. package/src/schema/literal.ts +20 -0
  215. package/src/schema/never.ts +14 -0
  216. package/src/schema/null.ts +20 -0
  217. package/src/schema/object.test.ts +138 -0
  218. package/src/schema/object.ts +180 -0
  219. package/src/schema/params.ts +157 -0
  220. package/src/schema/payload.ts +53 -0
  221. package/src/schema/permission-set.ts +22 -0
  222. package/src/schema/permission.ts +15 -0
  223. package/src/schema/procedure.ts +35 -0
  224. package/src/schema/query.ts +28 -0
  225. package/src/schema/record.ts +106 -0
  226. package/src/schema/ref.ts +47 -0
  227. package/src/schema/string.ts +139 -0
  228. package/src/schema/subscription.ts +35 -0
  229. package/src/schema/token.ts +41 -0
  230. package/src/schema/typed-object.ts +64 -0
  231. package/src/schema/typed-ref.ts +68 -0
  232. package/src/schema/typed-union.ts +106 -0
  233. package/src/schema/union.ts +40 -0
  234. package/src/schema/unknown-object.ts +20 -0
  235. package/src/schema/unknown.ts +10 -0
  236. package/src/schema.ts +40 -0
  237. package/src/util/array-agg.test.ts +41 -0
  238. package/src/util/array-agg.ts +43 -0
  239. package/src/validation/property-key.ts +1 -0
  240. package/src/validation/validation-error.ts +32 -0
  241. package/src/validation/validation-issue.ts +231 -0
  242. package/src/validation/validator.ts +361 -0
  243. package/src/validation.ts +4 -0
@@ -0,0 +1,180 @@
1
+ import { isPlainObject } from '@atproto/lex-data'
2
+ import { Simplify } from '../core.js'
3
+ import {
4
+ Infer,
5
+ ValidationResult,
6
+ Validator,
7
+ ValidatorContext,
8
+ } from '../validation.js'
9
+ import { DictSchema } from './dict.js'
10
+
11
+ export type ObjectSchemaProperties = { [_ in string]: Validator<any> }
12
+ export type ObjectSchemaOptions = {
13
+ required?: readonly string[]
14
+ nullable?: readonly string[]
15
+ unknownProperties?: 'strict' | DictSchema
16
+ }
17
+
18
+ export type ObjectSchemaNullValue<
19
+ O extends ObjectSchemaOptions,
20
+ K extends string,
21
+ > = O extends { nullable: readonly (infer N extends string)[] }
22
+ ? K extends N
23
+ ? null
24
+ : never
25
+ : never
26
+
27
+ export type ObjectSchemaPropertiesOutput<
28
+ P extends ObjectSchemaProperties,
29
+ O extends ObjectSchemaOptions,
30
+ > = O extends { required: readonly (infer R extends string)[] }
31
+ ? {
32
+ -readonly [K in string & keyof P & R]-?:
33
+ | Infer<P[K]>
34
+ | ObjectSchemaNullValue<O, K>
35
+ } & {
36
+ -readonly [K in Exclude<string & keyof P, R>]?:
37
+ | Infer<P[K]>
38
+ | ObjectSchemaNullValue<O, K>
39
+ }
40
+ : {
41
+ -readonly [K in string & keyof P]?:
42
+ | Infer<P[K]>
43
+ | ObjectSchemaNullValue<O, K>
44
+ }
45
+
46
+ /**
47
+ * Allows to more accurately represent the intersection of two object types
48
+ * where both types may share some keys, and one of them uses an index
49
+ * signature.
50
+ *
51
+ * @see {@link https://www.typescriptlang.org/play/?#code/C4TwDgpgBAglC8UDeUBmB7dAuKByARgIYBOuUAvlAGTJQDaA+lAJYB2UAzsMWwOYC6OVgFcAtvgjEKAKGkATCAGMANiWiL0rLlEI4YsjVuBQA1hBA4uPVrwRQARBnT2Dm7QDdCy4dESE6ZiD8UAD0IVAi4pJQABQcABbowspyUBIORMT2AJSyEAAeYOjExqCQUACSrMCSHErAzJoAPNJQsFAFNaxyHFAASkrFck1WfAA0UMKsJqzoAO6sAHxjrVAAQh35XT39g8TDozYTUzPzSyuLdqtwVKttMYHoqO00j88bnRDdvawQ7pJ3NpQAD860BbRwSHBQLadAA0ix2G91oJ1vDggAfWABcxPF5QOH8aFtci5aRlaAwVDMfIQVKIKo1Yh1RQNZq0Jw4AgkMjkCYoRiIzjcPioyISKTkRayBQqNRQQzaQgAMRpdL01NpclcRignm8EFVWrsKrVchxQVC4XF0SxmSAA Playground link}
52
+ */
53
+ type Intersect<
54
+ A extends Record<string, unknown>,
55
+ B extends Record<string, unknown>,
56
+ > = B[keyof B] extends never
57
+ ? A
58
+ : keyof A & keyof B extends never
59
+ ? // If A and B don't overlap, just return A & B
60
+ A & B
61
+ : // Otherwise, properly represent the fact that accessing using an
62
+ // index signature could return a value from either A or B
63
+ A & { [K in keyof B]: B[K] | A[keyof A & K] }
64
+
65
+ export type ObjectSchemaOutput<
66
+ P extends ObjectSchemaProperties,
67
+ O extends ObjectSchemaOptions,
68
+ > = O extends {
69
+ unknownProperties: Validator<infer D extends Record<string, unknown>>
70
+ }
71
+ ? Simplify<Intersect<ObjectSchemaPropertiesOutput<P, O>, D>>
72
+ : Simplify<ObjectSchemaPropertiesOutput<P, O>>
73
+
74
+ export class ObjectSchema<
75
+ const Validators extends ObjectSchemaProperties = any,
76
+ const Options extends ObjectSchemaOptions = any,
77
+ const Output extends ObjectSchemaOutput<
78
+ Validators,
79
+ Options
80
+ > = ObjectSchemaOutput<Validators, Options>,
81
+ > extends Validator<Output> {
82
+ constructor(
83
+ readonly validators: Validators,
84
+ readonly options: Options,
85
+ ) {
86
+ super()
87
+ }
88
+
89
+ get validatorsMap(): Map<string, Validator> {
90
+ const map = new Map(Object.entries(this.validators))
91
+
92
+ // Cache the map on the instance (to avoid re-creating it)
93
+ Object.defineProperty(this, 'validatorsMap', {
94
+ value: map,
95
+ writable: false,
96
+ enumerable: false,
97
+ configurable: true,
98
+ })
99
+
100
+ return map
101
+ }
102
+
103
+ override validateInContext(
104
+ input: unknown,
105
+ ctx: ValidatorContext,
106
+ ): ValidationResult<Output> {
107
+ if (!isPlainObject(input)) {
108
+ return ctx.issueInvalidType(input, ['object'])
109
+ }
110
+
111
+ // Lazily copy value
112
+ let copy: undefined | Record<string, unknown>
113
+
114
+ for (const [key, propDef] of this.validatorsMap) {
115
+ if (input[key] === null && this.options.nullable?.includes(key)) {
116
+ continue
117
+ }
118
+
119
+ const result = ctx.validateChild(input, key, propDef)
120
+ if (!result.success) {
121
+ // Because default values are provided by child validators, we need to
122
+ // run the validator to get the default value and, in case of failure,
123
+ // ignore validation error that were caused by missing keys.
124
+ if (!(key in input)) {
125
+ if (this.options.required?.includes(key)) {
126
+ // Transform into "required key" issue
127
+ return ctx.issueRequiredKey(input, key)
128
+ }
129
+
130
+ // Ignore missing non-required key
131
+ continue
132
+ }
133
+
134
+ return result
135
+ } else if (result.value === undefined) {
136
+ // Special case for validators that output "undefined" values (typically
137
+ // UnknownSchema) since they cannot differentiate between "missing key"
138
+ // and "key with undefined value"
139
+
140
+ if (!(key in input)) {
141
+ // Input was missing the key (was "undefined")
142
+ if (this.options.required?.includes(key)) {
143
+ return ctx.issueRequiredKey(input, key)
144
+ }
145
+
146
+ // Ignore missing non-required key
147
+ continue
148
+ }
149
+
150
+ // if "key" existed in input (would typically be "undefined"), we keep
151
+ // it as-is by continuing processing as if it was any other value.
152
+ }
153
+
154
+ if (result.value !== input[key]) {
155
+ copy ??= { ...input }
156
+ copy[key] = result.value
157
+ }
158
+ }
159
+
160
+ if (this.options.unknownProperties === 'strict') {
161
+ for (const key of Object.keys(input)) {
162
+ if (!this.validatorsMap.has(key)) {
163
+ return ctx.issueInvalidPropertyType(input, key, 'undefined')
164
+ }
165
+ }
166
+ } else if (this.options.unknownProperties) {
167
+ const result = this.options.unknownProperties.validateInContext(
168
+ copy ?? input,
169
+ ctx,
170
+ { ignoredKeys: this.validatorsMap },
171
+ )
172
+ if (!result.success) return result
173
+ if (result.value !== input) copy = result.value
174
+ }
175
+
176
+ const output = (copy ?? input) as Output
177
+
178
+ return ctx.success(output)
179
+ }
180
+ }
@@ -0,0 +1,157 @@
1
+ import { isPlainObject } from '@atproto/lex-data'
2
+ import { ValidationResult, Validator, ValidatorContext } from '../validation.js'
3
+ import { Param, ParamScalar, paramSchema } from './_parameters.js'
4
+ import { ObjectSchemaPropertiesOutput } from './object.js'
5
+ import { StringSchema } from './string.js'
6
+
7
+ export type ParamsSchemaProperties = {
8
+ [_ in string]: Validator<Param>
9
+ }
10
+
11
+ export type ParamsSchemaOptions = {
12
+ required?: readonly string[]
13
+ }
14
+
15
+ export type ParamsSchemaOutput<
16
+ P extends ParamsSchemaProperties,
17
+ O extends ParamsSchemaOptions,
18
+ > = ObjectSchemaPropertiesOutput<P, O>
19
+
20
+ export type InferParamsSchema<T> =
21
+ T extends ParamsSchema<infer P, infer O>
22
+ ? NonNullable<unknown> extends ParamsSchemaOutput<P, O>
23
+ ? ParamsSchemaOutput<P, O> | undefined
24
+ : ParamsSchemaOutput<P, O>
25
+ : never
26
+
27
+ export class ParamsSchema<
28
+ const Validators extends ParamsSchemaProperties = ParamsSchemaProperties,
29
+ const Options extends ParamsSchemaOptions = ParamsSchemaOptions,
30
+ Output extends ParamsSchemaOutput<Validators, Options> = ParamsSchemaOutput<
31
+ Validators,
32
+ Options
33
+ >,
34
+ > extends Validator<Output> {
35
+ readonly lexiconType = 'params' as const
36
+
37
+ constructor(
38
+ readonly validators: Validators,
39
+ readonly options: Options,
40
+ ) {
41
+ super()
42
+ }
43
+
44
+ get validatorsMap(): Map<string, Validator<Param>> {
45
+ const map = new Map(Object.entries(this.validators))
46
+
47
+ // Cache the map on the instance (to avoid re-creating it)
48
+ Object.defineProperty(this, 'validatorsMap', {
49
+ value: map,
50
+ writable: false,
51
+ enumerable: false,
52
+ configurable: true,
53
+ })
54
+
55
+ return map
56
+ }
57
+
58
+ override validateInContext(
59
+ input: unknown = {},
60
+ ctx: ValidatorContext,
61
+ ): ValidationResult<Output> {
62
+ if (!isPlainObject(input)) {
63
+ return ctx.issueInvalidType(input, 'object')
64
+ }
65
+
66
+ // Lazily copy value
67
+ let copy: undefined | Record<string, unknown>
68
+
69
+ // Ensure that non-specified params conform to param schema
70
+ for (const key in input) {
71
+ if (this.validatorsMap.has(key)) continue
72
+
73
+ const result = ctx.validateChild(input, key, paramSchema)
74
+ if (!result.success) return result
75
+
76
+ if (result.value !== input[key]) {
77
+ copy ??= { ...input }
78
+ copy[key] = result.value
79
+ }
80
+ }
81
+
82
+ for (const [key, propDef] of this.validatorsMap) {
83
+ const result = ctx.validateChild(input, key, propDef)
84
+ if (!result.success) {
85
+ // Because default values are provided by child validators, we need to
86
+ // run the validator to get the default value and, in case of failure,
87
+ // ignore validation error that were caused by missing keys.
88
+ if (!(key in input)) {
89
+ if (!this.options.required?.includes(key)) {
90
+ // Ignore missing non-required key
91
+ continue
92
+ } else {
93
+ // Transform into "required key" issue
94
+ return ctx.issueRequiredKey(input, key)
95
+ }
96
+ }
97
+
98
+ return result
99
+ }
100
+
101
+ if (result.value !== input[key]) {
102
+ // Copy on write
103
+ copy ??= { ...input }
104
+ copy[key] = result.value
105
+ }
106
+ }
107
+
108
+ return ctx.success((copy ?? input) as Output)
109
+ }
110
+
111
+ fromURLSearchParams(urlSearchParams: URLSearchParams): Output {
112
+ const params: Record<string, Param> = {}
113
+
114
+ for (const [key, value] of urlSearchParams.entries()) {
115
+ const validator = this.validatorsMap.get(key)
116
+
117
+ const coerced: ParamScalar =
118
+ validator != null && validator instanceof StringSchema
119
+ ? value
120
+ : value === 'true'
121
+ ? true
122
+ : value === 'false'
123
+ ? false
124
+ : /^-?\d+$/.test(value)
125
+ ? Number(value)
126
+ : value
127
+
128
+ if (params[key] === undefined) {
129
+ params[key] = coerced
130
+ } else if (Array.isArray(params[key])) {
131
+ params[key].push(coerced)
132
+ } else {
133
+ params[key] = [params[key] as ParamScalar, coerced]
134
+ }
135
+ }
136
+
137
+ return this.parse(params)
138
+ }
139
+
140
+ toURLSearchParams(input: Output): URLSearchParams {
141
+ const urlSearchParams = new URLSearchParams()
142
+
143
+ if (input !== undefined) {
144
+ for (const [key, value] of Object.entries(input)) {
145
+ if (Array.isArray(value)) {
146
+ for (const v of value) {
147
+ urlSearchParams.append(key, String(v))
148
+ }
149
+ } else if (value !== undefined) {
150
+ urlSearchParams.append(key, String(value))
151
+ }
152
+ }
153
+ }
154
+
155
+ return urlSearchParams
156
+ }
157
+ }
@@ -0,0 +1,53 @@
1
+ import { LexValue } from '@atproto/lex-data'
2
+ import { Validator } from '../validation.js'
3
+
4
+ export type LexBody<E extends string = any> = E extends `text/${string}`
5
+ ? string // Text encodings always yield string bodies
6
+ : E extends 'application/json'
7
+ ? LexValue
8
+ : Uint8Array
9
+
10
+ export type InferPayloadEncoding<P extends Payload> =
11
+ P extends Payload<infer E, any> ? E : undefined
12
+
13
+ export type InferPayloadBody<P extends Payload> =
14
+ P extends Payload<any, infer S>
15
+ ? S extends Validator<infer V>
16
+ ? V
17
+ : P extends Payload<infer E extends string>
18
+ ? LexBody<E>
19
+ : undefined
20
+ : undefined
21
+
22
+ export type PayloadOutput<
23
+ E extends string | undefined = any,
24
+ S extends Validator | undefined = any,
25
+ > = E extends string
26
+ ? S extends Validator<infer V>
27
+ ? {
28
+ encoding: E
29
+ body: V
30
+ }
31
+ : {
32
+ encoding: E
33
+ body: LexBody<E>
34
+ }
35
+ : void
36
+
37
+ export type PayloadBody<E extends string | undefined> = E extends undefined
38
+ ? undefined
39
+ : Validator | undefined
40
+
41
+ export class Payload<
42
+ const Encoding extends string | undefined = string | undefined,
43
+ const Body extends PayloadBody<Encoding> = PayloadBody<Encoding>,
44
+ > {
45
+ constructor(
46
+ readonly encoding: Encoding,
47
+ readonly schema: Body,
48
+ ) {
49
+ if (encoding === undefined && schema !== undefined) {
50
+ throw new TypeError('schema cannot be defined when encoding is undefined')
51
+ }
52
+ }
53
+ }
@@ -0,0 +1,22 @@
1
+ import { Permission } from './permission.js'
2
+
3
+ export type PermissionSetOptions = {
4
+ title?: string
5
+ 'title:lang'?: Record<string, undefined | string>
6
+ detail?: string
7
+ 'detail:lang'?: Record<string, undefined | string>
8
+ }
9
+
10
+ export class PermissionSet<
11
+ const Nsid extends string = any,
12
+ const Permissions extends readonly Permission[] = any,
13
+ const Options extends PermissionSetOptions = any,
14
+ > {
15
+ readonly lexiconType = 'permission-set' as const
16
+
17
+ constructor(
18
+ readonly nsid: Nsid,
19
+ readonly permissions: Permissions,
20
+ readonly options: Options,
21
+ ) {}
22
+ }
@@ -0,0 +1,15 @@
1
+ import { Params } from './_parameters.js'
2
+
3
+ export type PermissionOptions = Params
4
+
5
+ export class Permission<
6
+ const Resource extends string = any,
7
+ const Options extends PermissionOptions = any,
8
+ > {
9
+ readonly lexiconType = 'permission' as const
10
+
11
+ constructor(
12
+ readonly resource: Resource,
13
+ readonly options: Options,
14
+ ) {}
15
+ }
@@ -0,0 +1,35 @@
1
+ import { Nsid } from '../core.js'
2
+ import { Infer } from '../validation.js'
3
+ import { ParamsSchema } from './params.js'
4
+ import { InferPayloadBody, Payload } from './payload.js'
5
+
6
+ export type InferProcedureParameters<Q extends Procedure> =
7
+ Q extends Procedure<any, infer P extends ParamsSchema, any> ? Infer<P> : never
8
+
9
+ export type InferProcedureInputBody<Q extends Procedure> =
10
+ Q extends Procedure<any, any, infer I extends Payload, any>
11
+ ? InferPayloadBody<I>
12
+ : never
13
+
14
+ export type InferProcedureOutputBody<Q extends Procedure> =
15
+ Q extends Procedure<any, any, any, infer O extends Payload>
16
+ ? InferPayloadBody<O>
17
+ : never
18
+
19
+ export class Procedure<
20
+ N extends Nsid = any,
21
+ P extends ParamsSchema = any,
22
+ I extends Payload = any,
23
+ O extends Payload = any,
24
+ E extends undefined | readonly string[] = any,
25
+ > {
26
+ readonly lexiconType = 'procedure' as const
27
+
28
+ constructor(
29
+ readonly nsid: N,
30
+ readonly parameters: P,
31
+ readonly input: I,
32
+ readonly output: O,
33
+ readonly errors: E,
34
+ ) {}
35
+ }
@@ -0,0 +1,28 @@
1
+ import { Nsid } from '../core.js'
2
+ import { Infer } from '../validation.js'
3
+ import { ParamsSchema } from './params.js'
4
+ import { InferPayloadBody, Payload } from './payload.js'
5
+
6
+ export type InferQueryParameters<Q extends Query> =
7
+ Q extends Query<any, infer P extends ParamsSchema, any> ? Infer<P> : never
8
+
9
+ export type InferQueryOutputBody<Q extends Query> =
10
+ Q extends Query<any, any, infer O extends Payload>
11
+ ? InferPayloadBody<O>
12
+ : never
13
+
14
+ export class Query<
15
+ N extends Nsid = any,
16
+ P extends ParamsSchema = any,
17
+ O extends Payload = any,
18
+ E extends undefined | readonly string[] = any,
19
+ > {
20
+ readonly lexiconType = 'query' as const
21
+
22
+ constructor(
23
+ readonly nsid: N,
24
+ readonly parameters: P,
25
+ readonly output: O,
26
+ readonly errors: E,
27
+ ) {}
28
+ }
@@ -0,0 +1,106 @@
1
+ import { Nsid, RecordKey, Simplify } from '../core.js'
2
+ import {
3
+ Infer,
4
+ ValidationResult,
5
+ Validator,
6
+ ValidatorContext,
7
+ } from '../validation.js'
8
+ import { LiteralSchema } from './literal.js'
9
+ import { StringSchema } from './string.js'
10
+
11
+ export type InferRecordKey<R extends RecordSchema> =
12
+ R extends RecordSchema<infer K, any, any, any>
13
+ ? RecordKeySchemaOutput<K>
14
+ : never
15
+
16
+ export class RecordSchema<
17
+ Key extends RecordKey = any,
18
+ Type extends Nsid = any,
19
+ Schema extends Validator<object> = any,
20
+ Output extends Infer<Schema> & { $type: Type } = Infer<Schema> & {
21
+ $type: Type
22
+ },
23
+ > extends Validator<Output> {
24
+ readonly lexiconType = 'record' as const
25
+
26
+ keySchema: RecordKeySchema<Key>
27
+
28
+ constructor(
29
+ readonly key: Key,
30
+ readonly $type: Type,
31
+ readonly schema: Schema,
32
+ ) {
33
+ super()
34
+ this.keySchema = recordKey(key)
35
+ }
36
+
37
+ isTypeOf<X extends { $type?: unknown }>(
38
+ value: X,
39
+ ): value is X extends { $type: Type } ? X : never {
40
+ return value.$type === this.$type
41
+ }
42
+
43
+ build<X extends Omit<Output, '$type'>>(
44
+ input: X,
45
+ ): Simplify<Omit<X, '$type'> & { $type: Type }> {
46
+ return { ...input, $type: this.$type }
47
+ }
48
+
49
+ $isTypeOf<X extends { $type?: unknown }>(value: X) {
50
+ return this.isTypeOf<X>(value)
51
+ }
52
+
53
+ $build<X extends Omit<Output, '$type'>>(input: X) {
54
+ return this.build<X>(input)
55
+ }
56
+
57
+ override validateInContext(
58
+ input: unknown,
59
+ ctx: ValidatorContext,
60
+ ): ValidationResult<Output> {
61
+ const result = ctx.validate(input, this.schema) as ValidationResult<Output>
62
+
63
+ if (!result.success) {
64
+ return result
65
+ }
66
+
67
+ if (this.$type !== result.value.$type) {
68
+ return ctx.issueInvalidPropertyValue(result.value, '$type', [this.$type])
69
+ }
70
+
71
+ return result
72
+ }
73
+ }
74
+
75
+ export type RecordKeySchemaOutput<Key extends RecordKey> = Key extends 'any'
76
+ ? string
77
+ : Key extends 'tid'
78
+ ? string
79
+ : Key extends 'nsid'
80
+ ? Nsid
81
+ : Key extends `literal:${infer L extends string}`
82
+ ? L
83
+ : never
84
+
85
+ export type RecordKeySchema<Key extends RecordKey> = Validator<
86
+ RecordKeySchemaOutput<Key>
87
+ >
88
+
89
+ const keySchema = new StringSchema({ minLength: 1 })
90
+ const tidSchema = new StringSchema({ format: 'tid' })
91
+ const nsidSchema = new StringSchema({ format: 'nsid' })
92
+ const selfLiteralSchema = new LiteralSchema('self')
93
+
94
+ function recordKey<Key extends RecordKey>(key: Key): RecordKeySchema<Key> {
95
+ // @NOTE Use cached instances for common schemas
96
+ if (key === 'any') return keySchema as any
97
+ if (key === 'tid') return tidSchema as any
98
+ if (key === 'nsid') return nsidSchema as any
99
+ if (key.startsWith('literal:')) {
100
+ const value = key.slice(8) as RecordKeySchemaOutput<Key>
101
+ if (value === 'self') return selfLiteralSchema as any
102
+ return new LiteralSchema(value)
103
+ }
104
+
105
+ throw new Error(`Unsupported record key type: ${key}`)
106
+ }
@@ -0,0 +1,47 @@
1
+ import { ValidationResult, Validator, ValidatorContext } from '../validation.js'
2
+
3
+ export type RefSchemaGetter<V> = () => Validator<V>
4
+
5
+ export class RefSchema<V = any> extends Validator<V> {
6
+ readonly lexiconType = 'ref' as const
7
+
8
+ #getter: RefSchemaGetter<V>
9
+
10
+ constructor(getter: RefSchemaGetter<V>) {
11
+ // @NOTE In order to avoid circular dependency issues, we don't resolve
12
+ // the schema here. Instead, we resolve it lazily when first accessed.
13
+
14
+ super()
15
+
16
+ this.#getter = getter
17
+ }
18
+
19
+ get schema(): Validator<V> {
20
+ const value = this.#getter.call(null)
21
+
22
+ // Prevents a getter from depending on itself recursively, also allows GC to
23
+ // clean up the getter function.
24
+ this.#getter = throwAlreadyCalled
25
+
26
+ // Disable the getter and cache the resolved schema on the instance
27
+ Object.defineProperty(this, 'schema', {
28
+ value,
29
+ writable: false,
30
+ enumerable: false,
31
+ configurable: true,
32
+ })
33
+
34
+ return value
35
+ }
36
+
37
+ override validateInContext(
38
+ input: unknown,
39
+ ctx: ValidatorContext,
40
+ ): ValidationResult<V> {
41
+ return ctx.validate(input, this.schema)
42
+ }
43
+ }
44
+
45
+ function throwAlreadyCalled(): never {
46
+ throw new Error('RefSchema getter called multiple times')
47
+ }