@atproto/lex-builder 0.1.4 → 0.1.5
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 +12 -0
- package/dist/filtered-indexer.d.ts +2 -2
- package/dist/filtered-indexer.d.ts.map +1 -1
- package/dist/filtered-indexer.js.map +1 -1
- package/dist/formatter.d.ts +1 -1
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatter.js +1 -1
- package/dist/formatter.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lex-builder.d.ts +4 -4
- package/dist/lex-builder.d.ts.map +1 -1
- package/dist/lex-builder.js +53 -2
- package/dist/lex-builder.js.map +1 -1
- package/dist/lex-def-builder.d.ts +2 -2
- package/dist/lex-def-builder.d.ts.map +1 -1
- package/dist/lex-def-builder.js +1 -1
- package/dist/lex-def-builder.js.map +1 -1
- package/dist/lexicon-directory-indexer.d.ts.map +1 -1
- package/dist/lexicon-directory-indexer.js.map +1 -1
- package/dist/ref-resolver.d.ts +1 -1
- package/dist/ref-resolver.d.ts.map +1 -1
- package/dist/ref-resolver.js +1 -0
- package/dist/ref-resolver.js.map +1 -1
- package/package.json +6 -10
- package/src/filter.ts +0 -96
- package/src/filtered-indexer.test.ts +0 -84
- package/src/filtered-indexer.ts +0 -60
- package/src/formatter.ts +0 -83
- package/src/index.ts +0 -56
- package/src/lex-builder.ts +0 -299
- package/src/lex-def-builder.ts +0 -1035
- package/src/lexicon-directory-indexer.ts +0 -65
- package/src/polyfill.ts +0 -7
- package/src/ref-resolver.test.ts +0 -75
- package/src/ref-resolver.ts +0 -437
- package/src/ts-lang.ts +0 -197
- package/src/util.ts +0 -72
- package/tsconfig.build.json +0 -13
- package/tsconfig.json +0 -7
- package/tsconfig.tests.json +0 -8
package/src/lex-def-builder.ts
DELETED
|
@@ -1,1035 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
JSDocStructure,
|
|
3
|
-
OptionalKind,
|
|
4
|
-
SourceFile,
|
|
5
|
-
VariableDeclarationKind,
|
|
6
|
-
} from 'ts-morph'
|
|
7
|
-
import {
|
|
8
|
-
LexiconArray,
|
|
9
|
-
LexiconArrayItems,
|
|
10
|
-
LexiconBlob,
|
|
11
|
-
LexiconBoolean,
|
|
12
|
-
LexiconBytes,
|
|
13
|
-
LexiconCid,
|
|
14
|
-
LexiconDocument,
|
|
15
|
-
LexiconError,
|
|
16
|
-
LexiconIndexer,
|
|
17
|
-
LexiconInteger,
|
|
18
|
-
LexiconMessage,
|
|
19
|
-
LexiconObject,
|
|
20
|
-
LexiconParameters,
|
|
21
|
-
LexiconPayload,
|
|
22
|
-
LexiconPermissionSet,
|
|
23
|
-
LexiconProcedure,
|
|
24
|
-
LexiconQuery,
|
|
25
|
-
LexiconRecord,
|
|
26
|
-
LexiconRef,
|
|
27
|
-
LexiconRefUnion,
|
|
28
|
-
LexiconString,
|
|
29
|
-
LexiconSubscription,
|
|
30
|
-
LexiconToken,
|
|
31
|
-
LexiconUnknown,
|
|
32
|
-
} from '@atproto/lex-document'
|
|
33
|
-
import { l } from '@atproto/lex-schema'
|
|
34
|
-
import {
|
|
35
|
-
RefResolver,
|
|
36
|
-
RefResolverOptions,
|
|
37
|
-
ResolvedRef,
|
|
38
|
-
getPublicIdentifiers,
|
|
39
|
-
} from './ref-resolver.js'
|
|
40
|
-
import { asNamespaceExport } from './ts-lang.js'
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Configuration options for the {@link LexDefBuilder} class.
|
|
44
|
-
*
|
|
45
|
-
* @see {@link RefResolverOptions} for reference resolution options
|
|
46
|
-
*/
|
|
47
|
-
export type LexDefBuilderOptions = RefResolverOptions & {
|
|
48
|
-
/**
|
|
49
|
-
* The module specifier to use for importing the lexicon schema library.
|
|
50
|
-
*
|
|
51
|
-
* @default '@atproto/lex-schema'
|
|
52
|
-
*/
|
|
53
|
-
lib?: string
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Builds TypeScript type definitions and runtime schemas from a single
|
|
58
|
-
* Lexicon document.
|
|
59
|
-
*
|
|
60
|
-
* This class is responsible for generating the `.defs.ts` files that contain:
|
|
61
|
-
* - Type aliases for each lexicon definition
|
|
62
|
-
* - Runtime schema validators using `@atproto/lex-schema`
|
|
63
|
-
* - Utility functions for type checking and validation
|
|
64
|
-
* - Proper import statements for cross-references
|
|
65
|
-
*
|
|
66
|
-
* Each lexicon definition type (record, object, query, procedure, etc.)
|
|
67
|
-
* is handled with specialized code generation logic.
|
|
68
|
-
*/
|
|
69
|
-
export class LexDefBuilder {
|
|
70
|
-
private readonly refResolver: RefResolver
|
|
71
|
-
|
|
72
|
-
constructor(
|
|
73
|
-
private readonly options: LexDefBuilderOptions,
|
|
74
|
-
private readonly file: SourceFile,
|
|
75
|
-
private readonly doc: LexiconDocument,
|
|
76
|
-
indexer: LexiconIndexer,
|
|
77
|
-
) {
|
|
78
|
-
this.refResolver = new RefResolver(doc, file, indexer, options)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
async build() {
|
|
82
|
-
this.file.addVariableStatement({
|
|
83
|
-
declarationKind: VariableDeclarationKind.Const,
|
|
84
|
-
declarations: [
|
|
85
|
-
{ name: '$nsid', initializer: JSON.stringify(this.doc.id) },
|
|
86
|
-
],
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
this.file.addExportDeclaration({
|
|
90
|
-
namedExports: [{ name: '$nsid' }],
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
const defs = Object.keys(this.doc.defs)
|
|
94
|
-
if (defs.length) {
|
|
95
|
-
const moduleSpecifier = this.options?.lib ?? '@atproto/lex-schema'
|
|
96
|
-
this.file
|
|
97
|
-
.addImportDeclaration({ moduleSpecifier })
|
|
98
|
-
.addNamedImports([{ name: 'l' }])
|
|
99
|
-
|
|
100
|
-
for (const hash of defs) {
|
|
101
|
-
await this.addDef(hash)
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private addUtils(definitions: Record<string, undefined | string>) {
|
|
107
|
-
for (const [name, initializer] of Object.entries(definitions)) {
|
|
108
|
-
if (initializer == null) continue
|
|
109
|
-
|
|
110
|
-
this.file.addVariableStatement({
|
|
111
|
-
isExported: true,
|
|
112
|
-
declarationKind: VariableDeclarationKind.Const,
|
|
113
|
-
declarations: [{ name, initializer }],
|
|
114
|
-
})
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
private async addDef(hash: string) {
|
|
119
|
-
const def = Object.hasOwn(this.doc.defs, hash) ? this.doc.defs[hash] : null
|
|
120
|
-
if (def == null) return
|
|
121
|
-
|
|
122
|
-
switch (def.type) {
|
|
123
|
-
case 'permission-set':
|
|
124
|
-
return this.addPermissionSet(hash, def)
|
|
125
|
-
case 'procedure':
|
|
126
|
-
return this.addProcedure(hash, def)
|
|
127
|
-
case 'query':
|
|
128
|
-
return this.addQuery(hash, def)
|
|
129
|
-
case 'subscription':
|
|
130
|
-
return this.addSubscription(hash, def)
|
|
131
|
-
case 'record':
|
|
132
|
-
return this.addRecord(hash, def)
|
|
133
|
-
case 'token':
|
|
134
|
-
return this.addToken(hash, def)
|
|
135
|
-
case 'object':
|
|
136
|
-
return this.addObject(hash, def)
|
|
137
|
-
case 'array':
|
|
138
|
-
return this.addArray(hash, def)
|
|
139
|
-
default:
|
|
140
|
-
await this.addSchema(hash, def, {
|
|
141
|
-
type: await this.compileContainedType(def),
|
|
142
|
-
schema: await this.compileContainedSchema(def),
|
|
143
|
-
validationUtils: true,
|
|
144
|
-
})
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
private async addPermissionSet(hash: string, def: LexiconPermissionSet) {
|
|
149
|
-
const permission = def.permissions.map((def) => {
|
|
150
|
-
const options = stringifyOptions(def, undefined, ['resource', 'type'])
|
|
151
|
-
return markPure(
|
|
152
|
-
`l.permission(${JSON.stringify(def.resource)}, ${options})`,
|
|
153
|
-
)
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
const options = stringifyOptions(def, [
|
|
157
|
-
'title',
|
|
158
|
-
'title:lang',
|
|
159
|
-
'detail',
|
|
160
|
-
'detail:lang',
|
|
161
|
-
] satisfies (keyof l.PermissionSetOptions)[])
|
|
162
|
-
|
|
163
|
-
await this.addSchema(hash, def, {
|
|
164
|
-
schema: markPure(
|
|
165
|
-
`l.permissionSet($nsid, [${permission.join(',')}], ${options})`,
|
|
166
|
-
),
|
|
167
|
-
})
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
private async addParameters(parameters?: LexiconParameters): Promise<string> {
|
|
171
|
-
const varName = '$params'
|
|
172
|
-
|
|
173
|
-
this.addUtils({
|
|
174
|
-
[varName]: await this.compileParamsSchema(parameters),
|
|
175
|
-
})
|
|
176
|
-
|
|
177
|
-
// @TODO Build the types instead of using an inferred type.
|
|
178
|
-
this.file.addTypeAlias({
|
|
179
|
-
isExported: true,
|
|
180
|
-
name: '$Params',
|
|
181
|
-
type: `l.InferOutput<typeof ${varName}>`,
|
|
182
|
-
docs: compileDocs(parameters?.description),
|
|
183
|
-
})
|
|
184
|
-
|
|
185
|
-
return varName
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
private async addInput(input?: LexiconPayload): Promise<string> {
|
|
189
|
-
const varName = '$input'
|
|
190
|
-
|
|
191
|
-
this.addUtils({
|
|
192
|
-
[varName]: await this.compilePayload(input),
|
|
193
|
-
})
|
|
194
|
-
|
|
195
|
-
// @TODO Build the types instead of using an inferred type.
|
|
196
|
-
this.file.addTypeAlias({
|
|
197
|
-
isExported: true,
|
|
198
|
-
name: '$Input<B = l.BinaryData>',
|
|
199
|
-
type: `l.InferPayload<typeof ${varName}, B>`,
|
|
200
|
-
docs: compileDocs(input?.description),
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
this.file.addTypeAlias({
|
|
204
|
-
isExported: true,
|
|
205
|
-
name: '$InputBody<B = l.BinaryData>',
|
|
206
|
-
type: `l.InferPayloadBody<typeof ${varName}, B>`,
|
|
207
|
-
docs: compileDocs(input?.description),
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
return varName
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
private async addOutput(output?: LexiconPayload): Promise<string> {
|
|
214
|
-
const varName = '$output'
|
|
215
|
-
|
|
216
|
-
this.addUtils({
|
|
217
|
-
[varName]: await this.compilePayload(output),
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
// @TODO Build the types instead of using an inferred type.
|
|
221
|
-
this.file.addTypeAlias({
|
|
222
|
-
isExported: true,
|
|
223
|
-
name: '$Output<B = l.BinaryData>',
|
|
224
|
-
type: `l.InferPayload<typeof ${varName}, B>`,
|
|
225
|
-
docs: compileDocs(output?.description),
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
this.file.addTypeAlias({
|
|
229
|
-
isExported: true,
|
|
230
|
-
name: '$OutputBody<B = l.BinaryData>',
|
|
231
|
-
type: `l.InferPayloadBody<typeof ${varName}, B>`,
|
|
232
|
-
docs: compileDocs(output?.description),
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
return varName
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
private async addMessage(message?: LexiconMessage) {
|
|
239
|
-
const varName = '$message'
|
|
240
|
-
|
|
241
|
-
this.addUtils({
|
|
242
|
-
[varName]: await this.compileBodySchema(message?.schema),
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
// @TODO Build the types instead of using an inferred type.
|
|
246
|
-
this.file.addTypeAlias({
|
|
247
|
-
isExported: true,
|
|
248
|
-
name: '$Message',
|
|
249
|
-
type: `l.InferOutput<typeof ${varName}>`,
|
|
250
|
-
docs: compileDocs(message?.description),
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
return varName
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private async addProcedure(hash: string, def: LexiconProcedure) {
|
|
257
|
-
if (hash !== 'main') {
|
|
258
|
-
throw new Error(`Definition ${hash} cannot be of type ${def.type}`)
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
// Declare each piece of the method as its own top-level exported const
|
|
262
|
-
// *before* `main`. This allows to export those pieces individually instead
|
|
263
|
-
// of "extracting" them from the "main" definition as below, which is bad
|
|
264
|
-
// for tree-shaking.
|
|
265
|
-
//
|
|
266
|
-
// export const $params = main.params`
|
|
267
|
-
|
|
268
|
-
const paramsVar = await this.addParameters(def.parameters)
|
|
269
|
-
const inputVar = await this.addInput(def.input)
|
|
270
|
-
const outputVar = await this.addOutput(def.output)
|
|
271
|
-
|
|
272
|
-
await this.addSchema(hash, def, {
|
|
273
|
-
schema: markPure(
|
|
274
|
-
`l.procedure($nsid, ${paramsVar}, ${inputVar}, ${outputVar}${formatErrorsArg(await this.compileErrors(def.errors))})`,
|
|
275
|
-
),
|
|
276
|
-
})
|
|
277
|
-
|
|
278
|
-
this.addUtils({
|
|
279
|
-
$lxm: '$nsid',
|
|
280
|
-
})
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
private async addQuery(hash: string, def: LexiconQuery) {
|
|
284
|
-
if (hash !== 'main') {
|
|
285
|
-
throw new Error(`Definition ${hash} cannot be of type ${def.type}`)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
// Declare each piece of the method as its own top-level exported const
|
|
289
|
-
// *before* `main`. This allows to export those pieces individually instead
|
|
290
|
-
// of "extracting" them from the "main" definition as below, which is bad
|
|
291
|
-
// for tree-shaking:
|
|
292
|
-
//
|
|
293
|
-
// export const $params = main.params
|
|
294
|
-
|
|
295
|
-
const paramsVar = await this.addParameters(def.parameters)
|
|
296
|
-
const outputVar = await this.addOutput(def.output)
|
|
297
|
-
|
|
298
|
-
await this.addSchema(hash, def, {
|
|
299
|
-
schema: markPure(
|
|
300
|
-
`l.query($nsid, ${paramsVar}, ${outputVar}${formatErrorsArg(await this.compileErrors(def.errors))})`,
|
|
301
|
-
),
|
|
302
|
-
})
|
|
303
|
-
|
|
304
|
-
this.addUtils({
|
|
305
|
-
$lxm: '$nsid',
|
|
306
|
-
})
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
private async addSubscription(hash: string, def: LexiconSubscription) {
|
|
310
|
-
if (hash !== 'main') {
|
|
311
|
-
throw new Error(`Definition ${hash} cannot be of type ${def.type}`)
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Declare each piece of the method as its own top-level exported const
|
|
315
|
-
// *before* `main`. This allows to export those pieces individually instead
|
|
316
|
-
// of "extracting" them from the "main" definition as below, which is bad
|
|
317
|
-
// for tree-shaking.
|
|
318
|
-
//
|
|
319
|
-
// export const $params = main.params`
|
|
320
|
-
|
|
321
|
-
const paramsVar = await this.addParameters(def.parameters)
|
|
322
|
-
const messageVar = await this.addMessage(def.message)
|
|
323
|
-
|
|
324
|
-
await this.addSchema(hash, def, {
|
|
325
|
-
schema: markPure(
|
|
326
|
-
`l.subscription($nsid, ${paramsVar}, ${messageVar}${formatErrorsArg(await this.compileErrors(def.errors))})`,
|
|
327
|
-
),
|
|
328
|
-
})
|
|
329
|
-
|
|
330
|
-
this.addUtils({
|
|
331
|
-
$lxm: '$nsid',
|
|
332
|
-
})
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
private async addRecord(hash: string, def: LexiconRecord) {
|
|
336
|
-
if (hash !== 'main') {
|
|
337
|
-
throw new Error(`Definition ${hash} cannot be of type ${def.type}`)
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
const key = JSON.stringify(def.key ?? 'any')
|
|
341
|
-
const objectSchema = await this.compileObjectSchema(def.record)
|
|
342
|
-
|
|
343
|
-
const properties = await this.compilePropertiesTypes(def.record)
|
|
344
|
-
properties.unshift(`$type: ${JSON.stringify(l.$type(this.doc.id, hash))}`)
|
|
345
|
-
|
|
346
|
-
await this.addSchema(hash, def, {
|
|
347
|
-
type: `{ ${properties.join(';')} }`,
|
|
348
|
-
schema: (ref) =>
|
|
349
|
-
markPure(
|
|
350
|
-
`l.record<${key}, ${ref.typeName}>(${key}, $nsid, ${objectSchema})`,
|
|
351
|
-
),
|
|
352
|
-
objectUtils: true,
|
|
353
|
-
validationUtils: true,
|
|
354
|
-
})
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
private async addObject(hash: string, def: LexiconObject) {
|
|
358
|
-
const objectSchema = await this.compileObjectSchema(def)
|
|
359
|
-
|
|
360
|
-
const properties = await this.compilePropertiesTypes(def)
|
|
361
|
-
properties.unshift(`$type?: ${JSON.stringify(l.$type(this.doc.id, hash))}`)
|
|
362
|
-
|
|
363
|
-
await this.addSchema(hash, def, {
|
|
364
|
-
type: `{ ${properties.join(';')} }`,
|
|
365
|
-
schema: (ref) =>
|
|
366
|
-
markPure(
|
|
367
|
-
`l.typedObject<${ref.typeName}>($nsid, ${JSON.stringify(hash)}, ${objectSchema})`,
|
|
368
|
-
),
|
|
369
|
-
objectUtils: true,
|
|
370
|
-
validationUtils: true,
|
|
371
|
-
})
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
private async addToken(hash: string, def: LexiconToken) {
|
|
375
|
-
await this.addSchema(hash, def, {
|
|
376
|
-
schema: markPure(`l.token($nsid, ${JSON.stringify(hash)})`),
|
|
377
|
-
type: JSON.stringify(l.$type(this.doc.id, hash)),
|
|
378
|
-
validationUtils: true,
|
|
379
|
-
})
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
private async addArray(hash: string, def: LexiconArray) {
|
|
383
|
-
// @TODO It could be nice to expose the array item type as a separate type.
|
|
384
|
-
// This was not done (yet) as there is no easy way to name it to avoid
|
|
385
|
-
// collisions.
|
|
386
|
-
|
|
387
|
-
const itemSchema = await this.compileContainedSchema(def.items)
|
|
388
|
-
const options = stringifyOptions(def, [
|
|
389
|
-
'minLength',
|
|
390
|
-
'maxLength',
|
|
391
|
-
] satisfies (keyof l.ArraySchemaOptions)[])
|
|
392
|
-
|
|
393
|
-
await this.addSchema(hash, def, {
|
|
394
|
-
type: `(${await this.compileContainedType(def.items)})[]`,
|
|
395
|
-
// @NOTE Not using compileArraySchema to allow specifying the generic
|
|
396
|
-
// parameter to l.array<>.
|
|
397
|
-
schema: (ref) =>
|
|
398
|
-
markPure(`l.array<${ref.typeName}[number]>(${itemSchema}, ${options})`),
|
|
399
|
-
validationUtils: true,
|
|
400
|
-
})
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
private async addSchema(
|
|
404
|
-
hash: string,
|
|
405
|
-
def: { description?: string },
|
|
406
|
-
{
|
|
407
|
-
type,
|
|
408
|
-
schema,
|
|
409
|
-
objectUtils,
|
|
410
|
-
validationUtils,
|
|
411
|
-
}: {
|
|
412
|
-
type?: string | ((ref: ResolvedRef) => string)
|
|
413
|
-
schema?: string | ((ref: ResolvedRef) => string)
|
|
414
|
-
objectUtils?: boolean
|
|
415
|
-
validationUtils?: boolean
|
|
416
|
-
},
|
|
417
|
-
): Promise<ResolvedRef> {
|
|
418
|
-
const ref = await this.refResolver.resolveLocal(hash)
|
|
419
|
-
const pub = getPublicIdentifiers(hash)
|
|
420
|
-
|
|
421
|
-
if (type) {
|
|
422
|
-
this.file.addTypeAlias({
|
|
423
|
-
name: ref.typeName,
|
|
424
|
-
type: typeof type === 'function' ? type(ref) : type,
|
|
425
|
-
docs: compileDocs(def.description),
|
|
426
|
-
})
|
|
427
|
-
|
|
428
|
-
this.file.addExportDeclaration({
|
|
429
|
-
isTypeOnly: true,
|
|
430
|
-
namedExports: [
|
|
431
|
-
{
|
|
432
|
-
name: ref.typeName,
|
|
433
|
-
alias:
|
|
434
|
-
ref.typeName === pub.typeName
|
|
435
|
-
? undefined
|
|
436
|
-
: asNamespaceExport(pub.typeName),
|
|
437
|
-
},
|
|
438
|
-
],
|
|
439
|
-
})
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
if (schema) {
|
|
443
|
-
this.file.addVariableStatement({
|
|
444
|
-
declarationKind: VariableDeclarationKind.Const,
|
|
445
|
-
declarations: [
|
|
446
|
-
{
|
|
447
|
-
name: ref.varName,
|
|
448
|
-
initializer: typeof schema === 'function' ? schema(ref) : schema,
|
|
449
|
-
},
|
|
450
|
-
],
|
|
451
|
-
docs: compileDocs(def.description),
|
|
452
|
-
})
|
|
453
|
-
|
|
454
|
-
this.file.addExportDeclaration({
|
|
455
|
-
namedExports: [
|
|
456
|
-
{
|
|
457
|
-
name: ref.varName,
|
|
458
|
-
alias:
|
|
459
|
-
ref.varName === pub.varName
|
|
460
|
-
? undefined
|
|
461
|
-
: asNamespaceExport(pub.varName),
|
|
462
|
-
},
|
|
463
|
-
],
|
|
464
|
-
})
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
if (hash === 'main' && objectUtils) {
|
|
468
|
-
this.addUtils({
|
|
469
|
-
$type: `$nsid`,
|
|
470
|
-
$isTypeOf: markPure(`${ref.varName}.isTypeOf.bind(${ref.varName})`),
|
|
471
|
-
$build: markPure(`${ref.varName}.build.bind(${ref.varName})`),
|
|
472
|
-
})
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
if (hash === 'main' && validationUtils) {
|
|
476
|
-
this.addUtils({
|
|
477
|
-
$assert: markPure(`${ref.varName}.assert.bind(${ref.varName})`),
|
|
478
|
-
$check: markPure(`${ref.varName}.check.bind(${ref.varName})`),
|
|
479
|
-
$cast: markPure(`${ref.varName}.cast.bind(${ref.varName})`),
|
|
480
|
-
$ifMatches: markPure(`${ref.varName}.ifMatches.bind(${ref.varName})`),
|
|
481
|
-
$matches: markPure(`${ref.varName}.matches.bind(${ref.varName})`),
|
|
482
|
-
$parse: markPure(`${ref.varName}.parse.bind(${ref.varName})`),
|
|
483
|
-
$safeParse: markPure(`${ref.varName}.safeParse.bind(${ref.varName})`),
|
|
484
|
-
$validate: markPure(`${ref.varName}.validate.bind(${ref.varName})`),
|
|
485
|
-
$safeValidate: markPure(
|
|
486
|
-
`${ref.varName}.safeValidate.bind(${ref.varName})`,
|
|
487
|
-
),
|
|
488
|
-
})
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
return ref
|
|
492
|
-
}
|
|
493
|
-
|
|
494
|
-
private async compilePayload(def: LexiconPayload | undefined) {
|
|
495
|
-
if (!def) return markPure(`l.payload()`)
|
|
496
|
-
|
|
497
|
-
// Special case for JSON object payloads
|
|
498
|
-
if (def.encoding === 'application/json' && def.schema?.type === 'object') {
|
|
499
|
-
const properties = await this.compilePropertiesSchemas(def.schema)
|
|
500
|
-
return markPure(`l.jsonPayload({${properties.join(',')}})`)
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
const encodedEncoding = JSON.stringify(def.encoding)
|
|
504
|
-
if (def.schema) {
|
|
505
|
-
const bodySchema = await this.compileBodySchema(def.schema)
|
|
506
|
-
return markPure(`l.payload(${encodedEncoding}, ${bodySchema})`)
|
|
507
|
-
} else {
|
|
508
|
-
return markPure(`l.payload(${encodedEncoding})`)
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
private async compileBodySchema(
|
|
513
|
-
def?: LexiconRef | LexiconRefUnion | LexiconObject,
|
|
514
|
-
): Promise<string> {
|
|
515
|
-
if (!def) return 'undefined'
|
|
516
|
-
if (def.type === 'object') return this.compileObjectSchema(def)
|
|
517
|
-
return this.compileContainedSchema(def)
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
private async compileParamsSchema(def: undefined | LexiconParameters) {
|
|
521
|
-
if (!def) return markPure(`l.params()`)
|
|
522
|
-
|
|
523
|
-
const properties = await this.compilePropertiesSchemas(def)
|
|
524
|
-
return markPure(
|
|
525
|
-
properties.length === 0
|
|
526
|
-
? `l.params()`
|
|
527
|
-
: `l.params({${properties.join(',')}})`,
|
|
528
|
-
)
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
private async compileErrors(defs?: readonly LexiconError[]) {
|
|
532
|
-
if (!defs?.length) return ''
|
|
533
|
-
return JSON.stringify(defs.map((d) => d.name))
|
|
534
|
-
}
|
|
535
|
-
|
|
536
|
-
private async compileObjectSchema(def: LexiconObject): Promise<string> {
|
|
537
|
-
const properties = await this.compilePropertiesSchemas(def)
|
|
538
|
-
return markPure(`l.object({${properties.join(',')}})`)
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
private async compilePropertiesSchemas(options: {
|
|
542
|
-
properties: Record<string, LexiconArray | LexiconArrayItems>
|
|
543
|
-
required?: readonly string[]
|
|
544
|
-
nullable?: readonly string[]
|
|
545
|
-
}): Promise<string[]> {
|
|
546
|
-
for (const opt of ['required', 'nullable'] as const) {
|
|
547
|
-
if (options[opt]) {
|
|
548
|
-
for (const prop of options[opt]) {
|
|
549
|
-
if (!Object.hasOwn(options.properties, prop)) {
|
|
550
|
-
throw new Error(`No schema found for ${opt} property "${prop}"`)
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
|
|
556
|
-
return Promise.all(
|
|
557
|
-
Object.entries(options.properties).map((entry) => {
|
|
558
|
-
return this.compilePropertyEntrySchema(entry, options)
|
|
559
|
-
}),
|
|
560
|
-
)
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
private async compilePropertiesTypes(options: {
|
|
564
|
-
properties: Record<string, LexiconArray | LexiconArrayItems>
|
|
565
|
-
required?: readonly string[]
|
|
566
|
-
nullable?: readonly string[]
|
|
567
|
-
}) {
|
|
568
|
-
return Promise.all(
|
|
569
|
-
Object.entries(options.properties).map((entry) => {
|
|
570
|
-
return this.compilePropertyEntryType(entry, options)
|
|
571
|
-
}),
|
|
572
|
-
)
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
private async compilePropertyEntrySchema(
|
|
576
|
-
[key, def]: [string, LexiconArray | LexiconArrayItems],
|
|
577
|
-
options: {
|
|
578
|
-
required?: readonly string[]
|
|
579
|
-
nullable?: readonly string[]
|
|
580
|
-
},
|
|
581
|
-
) {
|
|
582
|
-
const isNullable = options.nullable?.includes(key)
|
|
583
|
-
const isRequired = options.required?.includes(key)
|
|
584
|
-
|
|
585
|
-
let schema = await this.compileContainedSchema(def)
|
|
586
|
-
|
|
587
|
-
if (isNullable) {
|
|
588
|
-
schema = markPure(`l.nullable(${schema})`)
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
if (!isRequired) {
|
|
592
|
-
schema = markPure(`l.optional(${schema})`)
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
return `${JSON.stringify(key)}:${schema}`
|
|
596
|
-
}
|
|
597
|
-
|
|
598
|
-
private async compilePropertyEntryType(
|
|
599
|
-
[key, def]: [string, LexiconArray | LexiconArrayItems],
|
|
600
|
-
options: {
|
|
601
|
-
required?: readonly string[]
|
|
602
|
-
nullable?: readonly string[]
|
|
603
|
-
},
|
|
604
|
-
) {
|
|
605
|
-
const isNullable = options.nullable?.includes(key)
|
|
606
|
-
const isRequired = options.required?.includes(key)
|
|
607
|
-
|
|
608
|
-
const optional = isRequired ? '' : '?'
|
|
609
|
-
const append = isNullable ? ' | null' : ''
|
|
610
|
-
|
|
611
|
-
const jsDoc = compileLeadingTrivia(def.description) || ''
|
|
612
|
-
const name = JSON.stringify(key)
|
|
613
|
-
const type = await this.compileContainedType(def)
|
|
614
|
-
|
|
615
|
-
return `${jsDoc}${name}${optional}:${type}${append}`
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
private async compileContainedSchema(
|
|
619
|
-
def: LexiconArray | LexiconArrayItems,
|
|
620
|
-
): Promise<string> {
|
|
621
|
-
switch (def.type) {
|
|
622
|
-
case 'unknown':
|
|
623
|
-
return this.compileUnknownSchema(def)
|
|
624
|
-
case 'boolean':
|
|
625
|
-
return this.compileBooleanSchema(def)
|
|
626
|
-
case 'integer':
|
|
627
|
-
return this.compileIntegerSchema(def)
|
|
628
|
-
case 'string':
|
|
629
|
-
return this.compileStringSchema(def)
|
|
630
|
-
case 'bytes':
|
|
631
|
-
return this.compileBytesSchema(def)
|
|
632
|
-
case 'blob':
|
|
633
|
-
return this.compileBlobSchema(def)
|
|
634
|
-
case 'cid-link':
|
|
635
|
-
return this.compileCidLinkSchema(def)
|
|
636
|
-
case 'ref':
|
|
637
|
-
return this.compileRefSchema(def)
|
|
638
|
-
case 'union':
|
|
639
|
-
return this.compileRefUnionSchema(def)
|
|
640
|
-
case 'array':
|
|
641
|
-
return this.compileArraySchema(def)
|
|
642
|
-
default:
|
|
643
|
-
// @ts-expect-error
|
|
644
|
-
throw new Error(`Unsupported def type: ${def.type}`)
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
private async compileContainedType(
|
|
649
|
-
def: LexiconArray | LexiconArrayItems,
|
|
650
|
-
): Promise<string> {
|
|
651
|
-
switch (def.type) {
|
|
652
|
-
case 'unknown':
|
|
653
|
-
return this.compileUnknownType(def)
|
|
654
|
-
case 'boolean':
|
|
655
|
-
return this.compileBooleanType(def)
|
|
656
|
-
case 'integer':
|
|
657
|
-
return this.compileIntegerType(def)
|
|
658
|
-
case 'string':
|
|
659
|
-
return this.compileStringType(def)
|
|
660
|
-
case 'bytes':
|
|
661
|
-
return this.compileBytesType(def)
|
|
662
|
-
case 'blob':
|
|
663
|
-
return this.compileBlobType(def)
|
|
664
|
-
case 'cid-link':
|
|
665
|
-
return this.compileCidLinkType(def)
|
|
666
|
-
case 'ref':
|
|
667
|
-
return this.compileRefType(def)
|
|
668
|
-
case 'union':
|
|
669
|
-
return this.compileRefUnionType(def)
|
|
670
|
-
case 'array':
|
|
671
|
-
return this.compileArrayType(def)
|
|
672
|
-
default:
|
|
673
|
-
// @ts-expect-error
|
|
674
|
-
throw new Error(`Unsupported def type: ${def.type}`)
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
|
|
678
|
-
private async compileArraySchema(def: LexiconArray): Promise<string> {
|
|
679
|
-
const itemSchema = await this.compileContainedSchema(def.items)
|
|
680
|
-
const options = stringifyOptions(def, [
|
|
681
|
-
'minLength',
|
|
682
|
-
'maxLength',
|
|
683
|
-
] satisfies (keyof l.ArraySchemaOptions)[])
|
|
684
|
-
return markPure(`l.array(${itemSchema}, ${options})`)
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
private async compileArrayType(def: LexiconArray): Promise<string> {
|
|
688
|
-
return `(${await this.compileContainedType(def.items)})[]`
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
private async compileUnknownSchema(_def: LexiconUnknown): Promise<string> {
|
|
692
|
-
return markPure(`l.lexMap()`)
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
private async compileUnknownType(_def: LexiconUnknown): Promise<string> {
|
|
696
|
-
return `l.LexMap`
|
|
697
|
-
}
|
|
698
|
-
|
|
699
|
-
private withDefault(schema: string, defaultValue: unknown) {
|
|
700
|
-
if (defaultValue === undefined) return schema
|
|
701
|
-
|
|
702
|
-
return markPure(`l.withDefault(${schema}, ${JSON.stringify(defaultValue)})`)
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
private async compileBooleanSchema(def: LexiconBoolean): Promise<string> {
|
|
706
|
-
const schema = l.boolean()
|
|
707
|
-
|
|
708
|
-
if (def.default !== undefined) {
|
|
709
|
-
schema.check(def.default)
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
if (hasConst(def)) return this.compileConstSchema(def)
|
|
713
|
-
|
|
714
|
-
return this.withDefault(markPure(`l.boolean()`), def.default)
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
private async compileBooleanType(def: LexiconBoolean): Promise<string> {
|
|
718
|
-
if (hasConst(def)) return this.compileConstType(def)
|
|
719
|
-
return 'boolean'
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
private async compileIntegerSchema(def: LexiconInteger): Promise<string> {
|
|
723
|
-
const schema = l.integer(def)
|
|
724
|
-
|
|
725
|
-
if (hasConst(def)) {
|
|
726
|
-
schema.check(def.const)
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
if (hasEnum(def)) {
|
|
730
|
-
for (const val of def.enum) schema.check(val)
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
if (def.default !== undefined) {
|
|
734
|
-
schema.check(def.default)
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
if (hasConst(def)) return this.compileConstSchema(def)
|
|
738
|
-
if (hasEnum(def)) return this.compileEnumSchema(def)
|
|
739
|
-
|
|
740
|
-
const options = stringifyOptions(def, [
|
|
741
|
-
'maximum',
|
|
742
|
-
'minimum',
|
|
743
|
-
] satisfies (keyof l.IntegerSchemaOptions)[])
|
|
744
|
-
|
|
745
|
-
return this.withDefault(markPure(`l.integer(${options})`), def.default)
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
private async compileIntegerType(def: LexiconInteger): Promise<string> {
|
|
749
|
-
if (hasConst(def)) return this.compileConstType(def)
|
|
750
|
-
if (hasEnum(def)) return this.compileEnumType(def)
|
|
751
|
-
|
|
752
|
-
return 'number'
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
private async compileStringSchema(def: LexiconString): Promise<string> {
|
|
756
|
-
const schema = l.string(def)
|
|
757
|
-
|
|
758
|
-
if (hasConst(def)) {
|
|
759
|
-
schema.check(def.const)
|
|
760
|
-
}
|
|
761
|
-
|
|
762
|
-
if (hasEnum(def)) {
|
|
763
|
-
for (const val of def.enum) schema.check(val)
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
if (def.default !== undefined) {
|
|
767
|
-
schema.check(def.default)
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
if (hasConst(def)) return this.compileConstSchema(def)
|
|
771
|
-
if (hasEnum(def)) return this.compileEnumSchema(def)
|
|
772
|
-
|
|
773
|
-
const runtimeOptions = [
|
|
774
|
-
'format',
|
|
775
|
-
'maxGraphemes',
|
|
776
|
-
'minGraphemes',
|
|
777
|
-
'maxLength',
|
|
778
|
-
'minLength',
|
|
779
|
-
// We don't want to include knownValues in the schema options **at
|
|
780
|
-
// runtime** as it has no effect and only causes bloat:
|
|
781
|
-
// "knownValues",
|
|
782
|
-
] as const satisfies (keyof l.StringSchemaOptions)[]
|
|
783
|
-
|
|
784
|
-
const options = stringifyOptions(def, runtimeOptions)
|
|
785
|
-
|
|
786
|
-
// We *do* however need knownValues for the inferred type, so we include it
|
|
787
|
-
// as the generic parameter. We only do this if the def has knownValues,
|
|
788
|
-
// otherwise we let TypeScript infer the options generic by not defining it.
|
|
789
|
-
const generic = def.knownValues
|
|
790
|
-
? stringifyOptions(def, [
|
|
791
|
-
...runtimeOptions,
|
|
792
|
-
'knownValues',
|
|
793
|
-
] satisfies (keyof l.StringSchemaOptions)[])
|
|
794
|
-
: undefined
|
|
795
|
-
|
|
796
|
-
return this.withDefault(
|
|
797
|
-
markPure(`l.string${generic ? `<${generic}>` : ''}(${options})`),
|
|
798
|
-
def.default,
|
|
799
|
-
)
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
private async compileStringType(def: LexiconString): Promise<string> {
|
|
803
|
-
if (hasConst(def)) return this.compileConstType(def)
|
|
804
|
-
if (hasEnum(def)) return this.compileEnumType(def)
|
|
805
|
-
|
|
806
|
-
switch (def.format) {
|
|
807
|
-
case undefined:
|
|
808
|
-
break
|
|
809
|
-
case 'datetime':
|
|
810
|
-
return 'l.DatetimeString'
|
|
811
|
-
case 'uri':
|
|
812
|
-
return 'l.UriString'
|
|
813
|
-
case 'at-uri':
|
|
814
|
-
return 'l.AtUriString'
|
|
815
|
-
case 'did':
|
|
816
|
-
return 'l.DidString'
|
|
817
|
-
case 'handle':
|
|
818
|
-
return 'l.HandleString'
|
|
819
|
-
case 'at-identifier':
|
|
820
|
-
return 'l.AtIdentifierString'
|
|
821
|
-
case 'nsid':
|
|
822
|
-
return 'l.NsidString'
|
|
823
|
-
case 'tid':
|
|
824
|
-
return 'l.TidString'
|
|
825
|
-
case 'cid':
|
|
826
|
-
return 'l.CidString'
|
|
827
|
-
case 'language':
|
|
828
|
-
return 'l.LanguageString'
|
|
829
|
-
case 'record-key':
|
|
830
|
-
return 'l.RecordKeyString'
|
|
831
|
-
default:
|
|
832
|
-
throw new Error(`Unknown string format: ${def.format}`)
|
|
833
|
-
}
|
|
834
|
-
|
|
835
|
-
if (def.knownValues?.length) {
|
|
836
|
-
return (
|
|
837
|
-
def.knownValues.map((v) => JSON.stringify(v)).join(' | ') +
|
|
838
|
-
' | l.UnknownString'
|
|
839
|
-
)
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
return 'string'
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
private async compileBytesSchema(def: LexiconBytes): Promise<string> {
|
|
846
|
-
const options = stringifyOptions(def, [
|
|
847
|
-
'minLength',
|
|
848
|
-
'maxLength',
|
|
849
|
-
] satisfies (keyof l.BytesSchemaOptions)[])
|
|
850
|
-
return markPure(`l.bytes(${options})`)
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
private async compileBytesType(_def: LexiconBytes): Promise<string> {
|
|
854
|
-
return 'Uint8Array'
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
private async compileBlobSchema(def: LexiconBlob): Promise<string> {
|
|
858
|
-
const options = stringifyOptions(def, [
|
|
859
|
-
'maxSize',
|
|
860
|
-
'accept',
|
|
861
|
-
] satisfies (keyof l.BlobSchemaOptions)[])
|
|
862
|
-
return markPure(`l.blob(${options})`)
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
private async compileBlobType(_def: LexiconBlob): Promise<string> {
|
|
866
|
-
return 'l.BlobRef'
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
private async compileCidLinkSchema(_def: LexiconCid): Promise<string> {
|
|
870
|
-
return markPure(`l.cid()`)
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
private async compileCidLinkType(_def: LexiconCid): Promise<string> {
|
|
874
|
-
return 'l.Cid'
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
private async compileRefSchema(def: LexiconRef): Promise<string> {
|
|
878
|
-
const { varName, typeName } = await this.refResolver.resolve(def.ref)
|
|
879
|
-
// @NOTE "as any" is needed in schemas with circular refs as TypeScript
|
|
880
|
-
// cannot infer the type of a value that depends on its initializer type
|
|
881
|
-
return markPure(`l.ref<${typeName}>((() => ${varName}) as any)`)
|
|
882
|
-
}
|
|
883
|
-
|
|
884
|
-
private async compileRefType(def: LexiconRef): Promise<string> {
|
|
885
|
-
const ref = await this.refResolver.resolve(def.ref)
|
|
886
|
-
return ref.typeName
|
|
887
|
-
}
|
|
888
|
-
|
|
889
|
-
private async compileRefUnionSchema(def: LexiconRefUnion): Promise<string> {
|
|
890
|
-
if (def.refs.length === 0 && def.closed) {
|
|
891
|
-
return markPure(`l.never()`)
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
const refs = await Promise.all(
|
|
895
|
-
def.refs.map(async (ref: string) => {
|
|
896
|
-
const { varName, typeName } = await this.refResolver.resolve(ref)
|
|
897
|
-
// @NOTE "as any" is needed in schemas with circular refs as TypeScript
|
|
898
|
-
// cannot infer the type of a value that depends on its initializer type
|
|
899
|
-
return markPure(`l.typedRef<${typeName}>((() => ${varName}) as any)`)
|
|
900
|
-
}),
|
|
901
|
-
)
|
|
902
|
-
|
|
903
|
-
return markPure(`l.typedUnion([${refs.join(',')}], ${def.closed ?? false})`)
|
|
904
|
-
}
|
|
905
|
-
|
|
906
|
-
private async compileRefUnionType(def: LexiconRefUnion): Promise<string> {
|
|
907
|
-
const types = await Promise.all(
|
|
908
|
-
def.refs.map(async (ref) => {
|
|
909
|
-
const { typeName } = await this.refResolver.resolve(ref)
|
|
910
|
-
return `l.$Typed<${typeName}>`
|
|
911
|
-
}),
|
|
912
|
-
)
|
|
913
|
-
if (!def.closed) types.push('l.Unknown$TypedObject')
|
|
914
|
-
return types.join(' | ') || 'never'
|
|
915
|
-
}
|
|
916
|
-
|
|
917
|
-
private async compileConstSchema<
|
|
918
|
-
T extends null | number | string | boolean,
|
|
919
|
-
>(def: { const: T; enum?: readonly T[]; default?: T }): Promise<string> {
|
|
920
|
-
if (hasEnum(def) && !def.enum.includes(def.const)) {
|
|
921
|
-
return markPure(`l.never()`)
|
|
922
|
-
}
|
|
923
|
-
|
|
924
|
-
const result = markPure(`l.literal(${JSON.stringify(def.const)})`)
|
|
925
|
-
|
|
926
|
-
return this.withDefault(result, def.default)
|
|
927
|
-
}
|
|
928
|
-
|
|
929
|
-
private async compileConstType<
|
|
930
|
-
T extends null | number | string | boolean,
|
|
931
|
-
>(def: { const: T; enum?: readonly T[] }): Promise<string> {
|
|
932
|
-
if (hasEnum(def) && !def.enum.includes(def.const)) {
|
|
933
|
-
return 'never'
|
|
934
|
-
}
|
|
935
|
-
return JSON.stringify(def.const)
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
private async compileEnumSchema<T extends null | number | string>(def: {
|
|
939
|
-
enum: readonly T[]
|
|
940
|
-
default?: T
|
|
941
|
-
}): Promise<string> {
|
|
942
|
-
if (def.enum.length === 0) {
|
|
943
|
-
return markPure(`l.never()`)
|
|
944
|
-
}
|
|
945
|
-
|
|
946
|
-
const result =
|
|
947
|
-
def.enum.length === 1
|
|
948
|
-
? markPure(`l.literal(${JSON.stringify(def.enum[0])})`)
|
|
949
|
-
: markPure(`l.enum(${JSON.stringify(def.enum)})`)
|
|
950
|
-
|
|
951
|
-
return this.withDefault(result, def.default)
|
|
952
|
-
}
|
|
953
|
-
|
|
954
|
-
private async compileEnumType<T extends null | number | string>(def: {
|
|
955
|
-
enum: readonly T[]
|
|
956
|
-
}): Promise<string> {
|
|
957
|
-
return def.enum.map((v) => JSON.stringify(v)).join(' | ') || 'never'
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
type ParsedDescription = OptionalKind<JSDocStructure> & {
|
|
962
|
-
description?: string
|
|
963
|
-
tags?: { tagName: string; text?: string }[]
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
function parseDescription(description: string): ParsedDescription {
|
|
967
|
-
if (/deprecated/i.test(description)) {
|
|
968
|
-
const deprecationMatch = description.match(
|
|
969
|
-
/(\s*deprecated\s*(?:--?|:)?\s*([^-]*)(?:-+)?)/i,
|
|
970
|
-
)
|
|
971
|
-
if (deprecationMatch) {
|
|
972
|
-
const { 1: match, 2: deprecationNotice } = deprecationMatch
|
|
973
|
-
return {
|
|
974
|
-
description: description.replace(match, '').trim() || undefined,
|
|
975
|
-
tags: [{ tagName: 'deprecated', text: deprecationNotice?.trim() }],
|
|
976
|
-
}
|
|
977
|
-
} else {
|
|
978
|
-
return {
|
|
979
|
-
description: description.trim() || undefined,
|
|
980
|
-
tags: [{ tagName: 'deprecated' }],
|
|
981
|
-
}
|
|
982
|
-
}
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
return {
|
|
986
|
-
description: description.trim() || undefined,
|
|
987
|
-
}
|
|
988
|
-
}
|
|
989
|
-
|
|
990
|
-
function compileLeadingTrivia(description?: string) {
|
|
991
|
-
if (!description) return undefined
|
|
992
|
-
const parsed = parseDescription(description)
|
|
993
|
-
if (!parsed.description && !parsed.tags?.length) return undefined
|
|
994
|
-
const tags = parsed.tags
|
|
995
|
-
?.map(({ tagName, text }) => (text ? `@${tagName} ${text}` : `@${tagName}`))
|
|
996
|
-
?.join('\n')
|
|
997
|
-
const text = `\n${[parsed.description, tags].filter(Boolean).join('\n\n')}`
|
|
998
|
-
return `\n\n/**${text.replaceAll('\n', '\n * ')}\n */\n`
|
|
999
|
-
}
|
|
1000
|
-
|
|
1001
|
-
function compileDocs(description?: string) {
|
|
1002
|
-
if (!description) return undefined
|
|
1003
|
-
return [parseDescription(description)]
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
function stringifyOptions<O extends Record<string, unknown>>(
|
|
1007
|
-
obj: O,
|
|
1008
|
-
include?: (keyof O)[],
|
|
1009
|
-
exclude?: (keyof O)[],
|
|
1010
|
-
) {
|
|
1011
|
-
const filtered = Object.entries(obj).filter(
|
|
1012
|
-
([k]) => (!include || include.includes(k)) && !exclude?.includes(k),
|
|
1013
|
-
)
|
|
1014
|
-
return filtered.length ? JSON.stringify(Object.fromEntries(filtered)) : ''
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
function hasConst<T extends { const?: unknown }>(
|
|
1018
|
-
def: T,
|
|
1019
|
-
): def is T & { const: NonNullable<T['const']> } {
|
|
1020
|
-
return def.const != null
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
function hasEnum<T extends { enum?: readonly unknown[] }>(
|
|
1024
|
-
def: T,
|
|
1025
|
-
): def is T & { enum: unknown[] } {
|
|
1026
|
-
return def.enum != null
|
|
1027
|
-
}
|
|
1028
|
-
|
|
1029
|
-
function markPure<T extends string>(v: T): `/*#__PURE__*/ ${T}` {
|
|
1030
|
-
return `/*#__PURE__*/ ${v}`
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
function formatErrorsArg(errors: string) {
|
|
1034
|
-
return errors ? `, ${errors}` : ''
|
|
1035
|
-
}
|