@gaearon/lex-builder 0.0.13
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 +144 -0
- package/dist/filter.d.ts +7 -0
- package/dist/filter.d.ts.map +1 -0
- package/dist/filter.js +30 -0
- package/dist/filter.js.map +1 -0
- package/dist/filtered-indexer.d.ts +2100 -0
- package/dist/filtered-indexer.d.ts.map +1 -0
- package/dist/filtered-indexer.js +56 -0
- package/dist/filtered-indexer.js.map +1 -0
- package/dist/formatter.d.ts +13 -0
- package/dist/formatter.d.ts.map +1 -0
- package/dist/formatter.js +34 -0
- package/dist/formatter.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/lex-builder.d.ts +36 -0
- package/dist/lex-builder.d.ts.map +1 -0
- package/dist/lex-builder.js +144 -0
- package/dist/lex-builder.js.map +1 -0
- package/dist/lex-def-builder.d.ts +69 -0
- package/dist/lex-def-builder.d.ts.map +1 -0
- package/dist/lex-def-builder.js +734 -0
- package/dist/lex-def-builder.js.map +1 -0
- package/dist/lexicon-directory-indexer.d.ts +11 -0
- package/dist/lexicon-directory-indexer.d.ts.map +1 -0
- package/dist/lexicon-directory-indexer.js +46 -0
- package/dist/lexicon-directory-indexer.js.map +1 -0
- package/dist/polyfill.d.ts +1 -0
- package/dist/polyfill.d.ts.map +1 -0
- package/dist/polyfill.js +7 -0
- package/dist/polyfill.js.map +1 -0
- package/dist/ref-resolver.d.ts +53 -0
- package/dist/ref-resolver.d.ts.map +1 -0
- package/dist/ref-resolver.js +277 -0
- package/dist/ref-resolver.js.map +1 -0
- package/dist/ts-lang.d.ts +6 -0
- package/dist/ts-lang.d.ts.map +1 -0
- package/dist/ts-lang.js +150 -0
- package/dist/ts-lang.js.map +1 -0
- package/dist/util.d.ts +12 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +72 -0
- package/dist/util.js.map +1 -0
- package/package.json +53 -0
- package/src/filter.ts +41 -0
- package/src/filtered-indexer.test.ts +84 -0
- package/src/filtered-indexer.ts +60 -0
- package/src/formatter.ts +42 -0
- package/src/index.ts +23 -0
- package/src/lex-builder.ts +186 -0
- package/src/lex-def-builder.ts +980 -0
- package/src/lexicon-directory-indexer.ts +52 -0
- package/src/polyfill.ts +7 -0
- package/src/ref-resolver.test.ts +75 -0
- package/src/ref-resolver.ts +368 -0
- package/src/ts-lang.ts +150 -0
- package/src/util.ts +72 -0
- package/tsconfig.build.json +13 -0
- package/tsconfig.json +7 -0
- package/tsconfig.tests.json +9 -0
|
@@ -0,0 +1,734 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.LexDefBuilder = void 0;
|
|
4
|
+
const ts_morph_1 = require("ts-morph");
|
|
5
|
+
const lex_schema_1 = require("@atproto/lex-schema");
|
|
6
|
+
const ref_resolver_js_1 = require("./ref-resolver.js");
|
|
7
|
+
const ts_lang_js_1 = require("./ts-lang.js");
|
|
8
|
+
/**
|
|
9
|
+
* Utility class to build a TypeScript source file from a lexicon document.
|
|
10
|
+
*/
|
|
11
|
+
class LexDefBuilder {
|
|
12
|
+
options;
|
|
13
|
+
file;
|
|
14
|
+
doc;
|
|
15
|
+
refResolver;
|
|
16
|
+
constructor(options, file, doc, indexer) {
|
|
17
|
+
this.options = options;
|
|
18
|
+
this.file = file;
|
|
19
|
+
this.doc = doc;
|
|
20
|
+
this.refResolver = new ref_resolver_js_1.RefResolver(doc, file, indexer, options);
|
|
21
|
+
}
|
|
22
|
+
pure(code) {
|
|
23
|
+
return this.options.pureAnnotations ? markPure(code) : code;
|
|
24
|
+
}
|
|
25
|
+
async build() {
|
|
26
|
+
this.file.addVariableStatement({
|
|
27
|
+
declarationKind: ts_morph_1.VariableDeclarationKind.Const,
|
|
28
|
+
declarations: [
|
|
29
|
+
{ name: '$nsid', initializer: JSON.stringify(this.doc.id) },
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
this.file.addExportDeclaration({
|
|
33
|
+
namedExports: [{ name: '$nsid' }],
|
|
34
|
+
});
|
|
35
|
+
const defs = Object.keys(this.doc.defs);
|
|
36
|
+
if (defs.length) {
|
|
37
|
+
const moduleSpecifier = this.options?.lib ?? '@atproto/lex-schema';
|
|
38
|
+
this.file
|
|
39
|
+
.addImportDeclaration({ moduleSpecifier })
|
|
40
|
+
.addNamedImports([{ name: 'l' }]);
|
|
41
|
+
for (const hash of defs) {
|
|
42
|
+
await this.addDef(hash);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
addUtils(definitions) {
|
|
47
|
+
const entries = Object.entries(definitions).filter((e) => e[1] != null);
|
|
48
|
+
if (entries.length) {
|
|
49
|
+
this.file.addVariableStatement({
|
|
50
|
+
isExported: true,
|
|
51
|
+
declarationKind: ts_morph_1.VariableDeclarationKind.Const,
|
|
52
|
+
declarations: entries.map(([name, initializer]) => ({
|
|
53
|
+
name,
|
|
54
|
+
initializer,
|
|
55
|
+
})),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async addDef(hash) {
|
|
60
|
+
const def = Object.hasOwn(this.doc.defs, hash) ? this.doc.defs[hash] : null;
|
|
61
|
+
if (def == null)
|
|
62
|
+
return;
|
|
63
|
+
switch (def.type) {
|
|
64
|
+
case 'permission-set':
|
|
65
|
+
return this.addPermissionSet(hash, def);
|
|
66
|
+
case 'procedure':
|
|
67
|
+
return this.addProcedure(hash, def);
|
|
68
|
+
case 'query':
|
|
69
|
+
return this.addQuery(hash, def);
|
|
70
|
+
case 'subscription':
|
|
71
|
+
return this.addSubscription(hash, def);
|
|
72
|
+
case 'record':
|
|
73
|
+
return this.addRecord(hash, def);
|
|
74
|
+
case 'token':
|
|
75
|
+
return this.addToken(hash, def);
|
|
76
|
+
case 'object':
|
|
77
|
+
return this.addObject(hash, def);
|
|
78
|
+
case 'array':
|
|
79
|
+
return this.addArray(hash, def);
|
|
80
|
+
default:
|
|
81
|
+
await this.addSchema(hash, def, {
|
|
82
|
+
type: await this.compileContainedType(def),
|
|
83
|
+
schema: await this.compileContainedSchema(def),
|
|
84
|
+
validationUtils: true,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async addPermissionSet(hash, def) {
|
|
89
|
+
const permission = def.permissions.map((def) => {
|
|
90
|
+
const options = stringifyOptions(def, undefined, ['resource', 'type']);
|
|
91
|
+
return this.pure(`l.permission(${JSON.stringify(def.resource)}, ${options})`);
|
|
92
|
+
});
|
|
93
|
+
const options = stringifyOptions(def, [
|
|
94
|
+
'title',
|
|
95
|
+
'title:lang',
|
|
96
|
+
'detail',
|
|
97
|
+
'detail:lang',
|
|
98
|
+
]);
|
|
99
|
+
await this.addSchema(hash, def, {
|
|
100
|
+
schema: this.pure(`l.permissionSet($nsid, [${permission.join(',')}], ${options})`),
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
async addProcedure(hash, def) {
|
|
104
|
+
if (hash !== 'main') {
|
|
105
|
+
throw new Error(`Definition ${hash} cannot be of type ${def.type}`);
|
|
106
|
+
}
|
|
107
|
+
// @TODO Build the types instead of using an inferred type.
|
|
108
|
+
const ref = await this.addSchema(hash, def, {
|
|
109
|
+
schema: this.pure(`
|
|
110
|
+
l.procedure(
|
|
111
|
+
$nsid,
|
|
112
|
+
${await this.compileParamsSchema(def.parameters)},
|
|
113
|
+
${await this.compilePayload(def.input)},
|
|
114
|
+
${await this.compilePayload(def.output)},
|
|
115
|
+
${await this.compileErrors(def.errors)}
|
|
116
|
+
)
|
|
117
|
+
`),
|
|
118
|
+
});
|
|
119
|
+
this.addMethodTypeUtils(ref, def);
|
|
120
|
+
this.addUtils({
|
|
121
|
+
$lxm: this.pure(`${ref.varName}.nsid`),
|
|
122
|
+
$params: this.pure(`${ref.varName}.parameters`),
|
|
123
|
+
$input: this.pure(`${ref.varName}.input`),
|
|
124
|
+
$output: this.pure(`${ref.varName}.output`),
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
async addQuery(hash, def) {
|
|
128
|
+
if (hash !== 'main') {
|
|
129
|
+
throw new Error(`Definition ${hash} cannot be of type ${def.type}`);
|
|
130
|
+
}
|
|
131
|
+
// @TODO Build the types instead of using an inferred type.
|
|
132
|
+
const ref = await this.addSchema(hash, def, {
|
|
133
|
+
schema: this.pure(`
|
|
134
|
+
l.query(
|
|
135
|
+
$nsid,
|
|
136
|
+
${await this.compileParamsSchema(def.parameters)},
|
|
137
|
+
${await this.compilePayload(def.output)},
|
|
138
|
+
${await this.compileErrors(def.errors)}
|
|
139
|
+
)
|
|
140
|
+
`),
|
|
141
|
+
});
|
|
142
|
+
this.addMethodTypeUtils(ref, def);
|
|
143
|
+
this.addUtils({
|
|
144
|
+
$lxm: this.pure(`${ref.varName}.nsid`),
|
|
145
|
+
$params: `${ref.varName}.parameters`,
|
|
146
|
+
$output: `${ref.varName}.output`,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
async addSubscription(hash, def) {
|
|
150
|
+
if (hash !== 'main') {
|
|
151
|
+
throw new Error(`Definition ${hash} cannot be of type ${def.type}`);
|
|
152
|
+
}
|
|
153
|
+
// @TODO Build the types instead of using an inferred type.
|
|
154
|
+
const ref = await this.addSchema(hash, def, {
|
|
155
|
+
schema: this.pure(`
|
|
156
|
+
l.subscription(
|
|
157
|
+
$nsid,
|
|
158
|
+
${await this.compileParamsSchema(def.parameters)},
|
|
159
|
+
${await this.compileBodySchema(def.message?.schema)},
|
|
160
|
+
${await this.compileErrors(def.errors)}
|
|
161
|
+
)
|
|
162
|
+
`),
|
|
163
|
+
});
|
|
164
|
+
this.addMethodTypeUtils(ref, def);
|
|
165
|
+
this.addUtils({
|
|
166
|
+
$lxm: this.pure(`${ref.varName}.nsid`),
|
|
167
|
+
$params: `${ref.varName}.parameters`,
|
|
168
|
+
$message: `${ref.varName}.message`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
addMethodTypeUtils(ref, def) {
|
|
172
|
+
this.file.addTypeAlias({
|
|
173
|
+
isExported: true,
|
|
174
|
+
name: 'Params',
|
|
175
|
+
type: `l.InferMethodParams<typeof ${ref.varName}>`,
|
|
176
|
+
docs: compileDocs(def.parameters?.description),
|
|
177
|
+
});
|
|
178
|
+
if (def.type === 'procedure') {
|
|
179
|
+
this.file.addTypeAlias({
|
|
180
|
+
isExported: true,
|
|
181
|
+
name: 'Input',
|
|
182
|
+
type: `l.InferMethodInput<typeof ${ref.varName}>`,
|
|
183
|
+
docs: compileDocs(def.input?.description),
|
|
184
|
+
});
|
|
185
|
+
this.file.addTypeAlias({
|
|
186
|
+
isExported: true,
|
|
187
|
+
name: 'InputBody',
|
|
188
|
+
type: `l.InferMethodInputBody<typeof ${ref.varName}>`,
|
|
189
|
+
docs: compileDocs(def.input?.description),
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (def.type === 'procedure' || def.type === 'query') {
|
|
193
|
+
this.file.addTypeAlias({
|
|
194
|
+
isExported: true,
|
|
195
|
+
name: 'Output',
|
|
196
|
+
type: `l.InferMethodOutput<typeof ${ref.varName}>`,
|
|
197
|
+
docs: compileDocs(def.output?.description),
|
|
198
|
+
});
|
|
199
|
+
this.file.addTypeAlias({
|
|
200
|
+
isExported: true,
|
|
201
|
+
name: 'OutputBody',
|
|
202
|
+
type: `l.InferMethodOutputBody<typeof ${ref.varName}>`,
|
|
203
|
+
docs: compileDocs(def.output?.description),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
if (def.type === 'subscription') {
|
|
207
|
+
this.file.addTypeAlias({
|
|
208
|
+
isExported: true,
|
|
209
|
+
name: 'Message',
|
|
210
|
+
type: `l.InferSubscriptionMessage<typeof ${ref.varName}>`,
|
|
211
|
+
docs: compileDocs(def.message?.description),
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
async addRecord(hash, def) {
|
|
216
|
+
if (hash !== 'main') {
|
|
217
|
+
throw new Error(`Definition ${hash} cannot be of type ${def.type}`);
|
|
218
|
+
}
|
|
219
|
+
const key = JSON.stringify(def.key ?? 'any');
|
|
220
|
+
const objectSchema = await this.compileObjectSchema(def.record);
|
|
221
|
+
const properties = await this.compilePropertiesTypes(def.record);
|
|
222
|
+
properties.unshift(`$type: ${JSON.stringify(lex_schema_1.l.$type(this.doc.id, hash))}`);
|
|
223
|
+
await this.addSchema(hash, def, {
|
|
224
|
+
type: `{ ${properties.join(';')} }`,
|
|
225
|
+
schema: (ref) => this.pure(`l.record<${key}, ${ref.typeName}>(${key}, $nsid, ${objectSchema})`),
|
|
226
|
+
objectUtils: true,
|
|
227
|
+
validationUtils: true,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
async addObject(hash, def) {
|
|
231
|
+
const objectSchema = await this.compileObjectSchema(def);
|
|
232
|
+
const properties = await this.compilePropertiesTypes(def);
|
|
233
|
+
properties.unshift(`$type?: ${JSON.stringify(lex_schema_1.l.$type(this.doc.id, hash))}`);
|
|
234
|
+
await this.addSchema(hash, def, {
|
|
235
|
+
type: `{ ${properties.join(';')} }`,
|
|
236
|
+
schema: (ref) => this.pure(`l.typedObject<${ref.typeName}>($nsid, ${JSON.stringify(hash)}, ${objectSchema})`),
|
|
237
|
+
objectUtils: true,
|
|
238
|
+
validationUtils: true,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
async addToken(hash, def) {
|
|
242
|
+
await this.addSchema(hash, def, {
|
|
243
|
+
schema: this.pure(`l.token($nsid, ${JSON.stringify(hash)})`),
|
|
244
|
+
type: JSON.stringify(lex_schema_1.l.$type(this.doc.id, hash)),
|
|
245
|
+
validationUtils: true,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
async addArray(hash, def) {
|
|
249
|
+
// @TODO It could be nice to expose the array item type as a separate type.
|
|
250
|
+
// This was not done (yet) as there is no easy way to name it to avoid
|
|
251
|
+
// collisions.
|
|
252
|
+
const itemSchema = await this.compileContainedSchema(def.items);
|
|
253
|
+
const options = stringifyOptions(def, [
|
|
254
|
+
'minLength',
|
|
255
|
+
'maxLength',
|
|
256
|
+
]);
|
|
257
|
+
await this.addSchema(hash, def, {
|
|
258
|
+
type: `(${await this.compileContainedType(def.items)})[]`,
|
|
259
|
+
// @NOTE Not using compileArraySchema to allow specifying the generic
|
|
260
|
+
// parameter to l.array<>.
|
|
261
|
+
schema: (ref) => this.pure(`l.array<${ref.typeName}[number]>(${itemSchema}, ${options})`),
|
|
262
|
+
validationUtils: true,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
async addSchema(hash, def, { type, schema, objectUtils, validationUtils, }) {
|
|
266
|
+
const ref = await this.refResolver.resolveLocal(hash);
|
|
267
|
+
const pub = (0, ref_resolver_js_1.getPublicIdentifiers)(hash);
|
|
268
|
+
if (type) {
|
|
269
|
+
this.file.addTypeAlias({
|
|
270
|
+
name: ref.typeName,
|
|
271
|
+
type: typeof type === 'function' ? type(ref) : type,
|
|
272
|
+
docs: compileDocs(def.description),
|
|
273
|
+
});
|
|
274
|
+
this.file.addExportDeclaration({
|
|
275
|
+
isTypeOnly: true,
|
|
276
|
+
namedExports: [
|
|
277
|
+
{
|
|
278
|
+
name: ref.typeName,
|
|
279
|
+
alias: ref.typeName === pub.typeName
|
|
280
|
+
? undefined
|
|
281
|
+
: (0, ts_lang_js_1.asNamespaceExport)(pub.typeName),
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
if (schema) {
|
|
287
|
+
this.file.addVariableStatement({
|
|
288
|
+
declarationKind: ts_morph_1.VariableDeclarationKind.Const,
|
|
289
|
+
declarations: [
|
|
290
|
+
{
|
|
291
|
+
name: ref.varName,
|
|
292
|
+
initializer: typeof schema === 'function' ? schema(ref) : schema,
|
|
293
|
+
},
|
|
294
|
+
],
|
|
295
|
+
docs: compileDocs(def.description),
|
|
296
|
+
});
|
|
297
|
+
this.file.addExportDeclaration({
|
|
298
|
+
namedExports: [
|
|
299
|
+
{
|
|
300
|
+
name: ref.varName,
|
|
301
|
+
alias: ref.varName === pub.varName
|
|
302
|
+
? undefined
|
|
303
|
+
: (0, ts_lang_js_1.asNamespaceExport)(pub.varName),
|
|
304
|
+
},
|
|
305
|
+
],
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
if (hash === 'main' && objectUtils) {
|
|
309
|
+
this.addUtils({
|
|
310
|
+
$isTypeOf: markPure(`${ref.varName}.isTypeOf.bind(${ref.varName})`),
|
|
311
|
+
$build: markPure(`${ref.varName}.build.bind(${ref.varName})`),
|
|
312
|
+
$type: markPure(`${ref.varName}.$type`),
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
if (hash === 'main' && validationUtils) {
|
|
316
|
+
this.addUtils({
|
|
317
|
+
$assert: markPure(`${ref.varName}.assert.bind(${ref.varName})`),
|
|
318
|
+
$check: markPure(`${ref.varName}.check.bind(${ref.varName})`),
|
|
319
|
+
$cast: markPure(`${ref.varName}.cast.bind(${ref.varName})`),
|
|
320
|
+
$ifMatches: markPure(`${ref.varName}.ifMatches.bind(${ref.varName})`),
|
|
321
|
+
$matches: markPure(`${ref.varName}.matches.bind(${ref.varName})`),
|
|
322
|
+
$parse: markPure(`${ref.varName}.parse.bind(${ref.varName})`),
|
|
323
|
+
$safeParse: markPure(`${ref.varName}.safeParse.bind(${ref.varName})`),
|
|
324
|
+
$validate: markPure(`${ref.varName}.validate.bind(${ref.varName})`),
|
|
325
|
+
$safeValidate: markPure(`${ref.varName}.safeValidate.bind(${ref.varName})`),
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
return ref;
|
|
329
|
+
}
|
|
330
|
+
async compilePayload(def) {
|
|
331
|
+
if (!def)
|
|
332
|
+
return this.pure(`l.payload()`);
|
|
333
|
+
// Special case for JSON object payloads
|
|
334
|
+
if (def.encoding === 'application/json' && def.schema?.type === 'object') {
|
|
335
|
+
const properties = await this.compilePropertiesSchemas(def.schema);
|
|
336
|
+
return this.pure(`l.jsonPayload({${properties.join(',')}})`);
|
|
337
|
+
}
|
|
338
|
+
const encodedEncoding = JSON.stringify(def.encoding);
|
|
339
|
+
if (def.schema) {
|
|
340
|
+
const bodySchema = await this.compileBodySchema(def.schema);
|
|
341
|
+
return this.pure(`l.payload(${encodedEncoding}, ${bodySchema})`);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
return this.pure(`l.payload(${encodedEncoding})`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
async compileBodySchema(def) {
|
|
348
|
+
if (!def)
|
|
349
|
+
return 'undefined';
|
|
350
|
+
if (def.type === 'object')
|
|
351
|
+
return this.compileObjectSchema(def);
|
|
352
|
+
return this.compileContainedSchema(def);
|
|
353
|
+
}
|
|
354
|
+
async compileParamsSchema(def) {
|
|
355
|
+
if (!def)
|
|
356
|
+
return this.pure(`l.params()`);
|
|
357
|
+
const properties = await this.compilePropertiesSchemas(def);
|
|
358
|
+
return this.pure(properties.length === 0
|
|
359
|
+
? `l.params()`
|
|
360
|
+
: `l.params({${properties.join(',')}})`);
|
|
361
|
+
}
|
|
362
|
+
async compileErrors(defs) {
|
|
363
|
+
if (!defs?.length)
|
|
364
|
+
return '';
|
|
365
|
+
return JSON.stringify(defs.map((d) => d.name));
|
|
366
|
+
}
|
|
367
|
+
async compileObjectSchema(def) {
|
|
368
|
+
const properties = await this.compilePropertiesSchemas(def);
|
|
369
|
+
return this.pure(`l.object({${properties.join(',')}})`);
|
|
370
|
+
}
|
|
371
|
+
async compilePropertiesSchemas(options) {
|
|
372
|
+
for (const opt of ['required', 'nullable']) {
|
|
373
|
+
if (options[opt]) {
|
|
374
|
+
for (const prop of options[opt]) {
|
|
375
|
+
if (!Object.hasOwn(options.properties, prop)) {
|
|
376
|
+
throw new Error(`No schema found for ${opt} property "${prop}"`);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
return Promise.all(Object.entries(options.properties).map((entry) => {
|
|
382
|
+
return this.compilePropertyEntrySchema(entry, options);
|
|
383
|
+
}));
|
|
384
|
+
}
|
|
385
|
+
async compilePropertiesTypes(options) {
|
|
386
|
+
return Promise.all(Object.entries(options.properties).map((entry) => {
|
|
387
|
+
return this.compilePropertyEntryType(entry, options);
|
|
388
|
+
}));
|
|
389
|
+
}
|
|
390
|
+
async compilePropertyEntrySchema([key, def], options) {
|
|
391
|
+
const isNullable = options.nullable?.includes(key);
|
|
392
|
+
const isRequired = options.required?.includes(key);
|
|
393
|
+
let schema = await this.compileContainedSchema(def);
|
|
394
|
+
if (isNullable) {
|
|
395
|
+
schema = this.pure(`l.nullable(${schema})`);
|
|
396
|
+
}
|
|
397
|
+
if (!isRequired) {
|
|
398
|
+
schema = this.pure(`l.optional(${schema})`);
|
|
399
|
+
}
|
|
400
|
+
return `${JSON.stringify(key)}:${schema}`;
|
|
401
|
+
}
|
|
402
|
+
async compilePropertyEntryType([key, def], options) {
|
|
403
|
+
const isNullable = options.nullable?.includes(key);
|
|
404
|
+
const isRequired = options.required?.includes(key);
|
|
405
|
+
const optional = isRequired ? '' : '?';
|
|
406
|
+
const append = isNullable ? ' | null' : '';
|
|
407
|
+
const jsDoc = compileLeadingTrivia(def.description) || '';
|
|
408
|
+
const name = JSON.stringify(key);
|
|
409
|
+
const type = await this.compileContainedType(def);
|
|
410
|
+
return `${jsDoc}${name}${optional}:${type}${append}`;
|
|
411
|
+
}
|
|
412
|
+
async compileContainedSchema(def) {
|
|
413
|
+
switch (def.type) {
|
|
414
|
+
case 'unknown':
|
|
415
|
+
return this.compileUnknownSchema(def);
|
|
416
|
+
case 'boolean':
|
|
417
|
+
return this.compileBooleanSchema(def);
|
|
418
|
+
case 'integer':
|
|
419
|
+
return this.compileIntegerSchema(def);
|
|
420
|
+
case 'string':
|
|
421
|
+
return this.compileStringSchema(def);
|
|
422
|
+
case 'bytes':
|
|
423
|
+
return this.compileBytesSchema(def);
|
|
424
|
+
case 'blob':
|
|
425
|
+
return this.compileBlobSchema(def);
|
|
426
|
+
case 'cid-link':
|
|
427
|
+
return this.compileCidLinkSchema(def);
|
|
428
|
+
case 'ref':
|
|
429
|
+
return this.compileRefSchema(def);
|
|
430
|
+
case 'union':
|
|
431
|
+
return this.compileRefUnionSchema(def);
|
|
432
|
+
case 'array':
|
|
433
|
+
return this.compileArraySchema(def);
|
|
434
|
+
default:
|
|
435
|
+
// @ts-expect-error
|
|
436
|
+
throw new Error(`Unsupported def type: ${def.type}`);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
async compileContainedType(def) {
|
|
440
|
+
switch (def.type) {
|
|
441
|
+
case 'unknown':
|
|
442
|
+
return this.compileUnknownType(def);
|
|
443
|
+
case 'boolean':
|
|
444
|
+
return this.compileBooleanType(def);
|
|
445
|
+
case 'integer':
|
|
446
|
+
return this.compileIntegerType(def);
|
|
447
|
+
case 'string':
|
|
448
|
+
return this.compileStringType(def);
|
|
449
|
+
case 'bytes':
|
|
450
|
+
return this.compileBytesType(def);
|
|
451
|
+
case 'blob':
|
|
452
|
+
return this.compileBlobType(def);
|
|
453
|
+
case 'cid-link':
|
|
454
|
+
return this.compileCidLinkType(def);
|
|
455
|
+
case 'ref':
|
|
456
|
+
return this.compileRefType(def);
|
|
457
|
+
case 'union':
|
|
458
|
+
return this.compileRefUnionType(def);
|
|
459
|
+
case 'array':
|
|
460
|
+
return this.compileArrayType(def);
|
|
461
|
+
default:
|
|
462
|
+
// @ts-expect-error
|
|
463
|
+
throw new Error(`Unsupported def type: ${def.type}`);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
async compileArraySchema(def) {
|
|
467
|
+
const itemSchema = await this.compileContainedSchema(def.items);
|
|
468
|
+
const options = stringifyOptions(def, [
|
|
469
|
+
'minLength',
|
|
470
|
+
'maxLength',
|
|
471
|
+
]);
|
|
472
|
+
return this.pure(`l.array(${itemSchema}, ${options})`);
|
|
473
|
+
}
|
|
474
|
+
async compileArrayType(def) {
|
|
475
|
+
return `(${await this.compileContainedType(def.items)})[]`;
|
|
476
|
+
}
|
|
477
|
+
async compileUnknownSchema(_def) {
|
|
478
|
+
return this.pure(`l.unknownObject()`);
|
|
479
|
+
}
|
|
480
|
+
async compileUnknownType(_def) {
|
|
481
|
+
return `l.UnknownObject`;
|
|
482
|
+
}
|
|
483
|
+
withDefault(schema, defaultValue) {
|
|
484
|
+
if (defaultValue === undefined)
|
|
485
|
+
return schema;
|
|
486
|
+
return this.pure(`l.withDefault(${schema}, ${JSON.stringify(defaultValue)})`);
|
|
487
|
+
}
|
|
488
|
+
async compileBooleanSchema(def) {
|
|
489
|
+
const schema = lex_schema_1.l.boolean();
|
|
490
|
+
if (def.default !== undefined) {
|
|
491
|
+
schema.check(def.default);
|
|
492
|
+
}
|
|
493
|
+
if (hasConst(def))
|
|
494
|
+
return this.compileConstSchema(def);
|
|
495
|
+
return this.withDefault(this.pure(`l.boolean()`), def.default);
|
|
496
|
+
}
|
|
497
|
+
async compileBooleanType(def) {
|
|
498
|
+
if (hasConst(def))
|
|
499
|
+
return this.compileConstType(def);
|
|
500
|
+
return 'boolean';
|
|
501
|
+
}
|
|
502
|
+
async compileIntegerSchema(def) {
|
|
503
|
+
const schema = lex_schema_1.l.integer(def);
|
|
504
|
+
if (hasConst(def)) {
|
|
505
|
+
schema.check(def.const);
|
|
506
|
+
}
|
|
507
|
+
if (hasEnum(def)) {
|
|
508
|
+
for (const val of def.enum)
|
|
509
|
+
schema.check(val);
|
|
510
|
+
}
|
|
511
|
+
if (def.default !== undefined) {
|
|
512
|
+
schema.check(def.default);
|
|
513
|
+
}
|
|
514
|
+
if (hasConst(def))
|
|
515
|
+
return this.compileConstSchema(def);
|
|
516
|
+
if (hasEnum(def))
|
|
517
|
+
return this.compileEnumSchema(def);
|
|
518
|
+
const options = stringifyOptions(def, [
|
|
519
|
+
'maximum',
|
|
520
|
+
'minimum',
|
|
521
|
+
]);
|
|
522
|
+
return this.withDefault(this.pure(`l.integer(${options})`), def.default);
|
|
523
|
+
}
|
|
524
|
+
async compileIntegerType(def) {
|
|
525
|
+
if (hasConst(def))
|
|
526
|
+
return this.compileConstType(def);
|
|
527
|
+
if (hasEnum(def))
|
|
528
|
+
return this.compileEnumType(def);
|
|
529
|
+
return 'number';
|
|
530
|
+
}
|
|
531
|
+
async compileStringSchema(def) {
|
|
532
|
+
const schema = lex_schema_1.l.string(def);
|
|
533
|
+
if (hasConst(def)) {
|
|
534
|
+
schema.check(def.const);
|
|
535
|
+
}
|
|
536
|
+
if (hasEnum(def)) {
|
|
537
|
+
for (const val of def.enum)
|
|
538
|
+
schema.check(val);
|
|
539
|
+
}
|
|
540
|
+
if (def.default !== undefined) {
|
|
541
|
+
schema.check(def.default);
|
|
542
|
+
}
|
|
543
|
+
if (hasConst(def))
|
|
544
|
+
return this.compileConstSchema(def);
|
|
545
|
+
if (hasEnum(def))
|
|
546
|
+
return this.compileEnumSchema(def);
|
|
547
|
+
const options = stringifyOptions(def, [
|
|
548
|
+
'format',
|
|
549
|
+
'maxGraphemes',
|
|
550
|
+
'minGraphemes',
|
|
551
|
+
'maxLength',
|
|
552
|
+
'minLength',
|
|
553
|
+
]);
|
|
554
|
+
return this.withDefault(this.pure(`l.string(${options})`), def.default);
|
|
555
|
+
}
|
|
556
|
+
async compileStringType(def) {
|
|
557
|
+
if (hasConst(def))
|
|
558
|
+
return this.compileConstType(def);
|
|
559
|
+
if (hasEnum(def))
|
|
560
|
+
return this.compileEnumType(def);
|
|
561
|
+
switch (def.format) {
|
|
562
|
+
case undefined:
|
|
563
|
+
break;
|
|
564
|
+
case 'datetime':
|
|
565
|
+
return 'l.DatetimeString';
|
|
566
|
+
case 'uri':
|
|
567
|
+
return 'l.UriString';
|
|
568
|
+
case 'at-uri':
|
|
569
|
+
return 'l.AtUriString';
|
|
570
|
+
case 'did':
|
|
571
|
+
return 'l.DidString';
|
|
572
|
+
case 'handle':
|
|
573
|
+
return 'l.HandleString';
|
|
574
|
+
case 'at-identifier':
|
|
575
|
+
return 'l.AtIdentifierString';
|
|
576
|
+
case 'nsid':
|
|
577
|
+
return 'l.NsidString';
|
|
578
|
+
case 'tid':
|
|
579
|
+
return 'l.TidString';
|
|
580
|
+
case 'cid':
|
|
581
|
+
return 'l.CidString';
|
|
582
|
+
case 'language':
|
|
583
|
+
return 'l.LanguageString';
|
|
584
|
+
case 'record-key':
|
|
585
|
+
return 'l.RecordKeyString';
|
|
586
|
+
default:
|
|
587
|
+
throw new Error(`Unknown string format: ${def.format}`);
|
|
588
|
+
}
|
|
589
|
+
if (def.knownValues?.length) {
|
|
590
|
+
return (def.knownValues.map((v) => JSON.stringify(v)).join(' | ') +
|
|
591
|
+
' | l.UnknownString');
|
|
592
|
+
}
|
|
593
|
+
return 'string';
|
|
594
|
+
}
|
|
595
|
+
async compileBytesSchema(def) {
|
|
596
|
+
const options = stringifyOptions(def, [
|
|
597
|
+
'minLength',
|
|
598
|
+
'maxLength',
|
|
599
|
+
]);
|
|
600
|
+
return this.pure(`l.bytes(${options})`);
|
|
601
|
+
}
|
|
602
|
+
async compileBytesType(_def) {
|
|
603
|
+
return 'Uint8Array';
|
|
604
|
+
}
|
|
605
|
+
async compileBlobSchema(def) {
|
|
606
|
+
const opts = { ...def, allowLegacy: this.options.allowLegacyBlobs === true };
|
|
607
|
+
const options = stringifyOptions(opts, [
|
|
608
|
+
'maxSize',
|
|
609
|
+
'accept',
|
|
610
|
+
'allowLegacy',
|
|
611
|
+
]);
|
|
612
|
+
return this.pure(`l.blob(${options})`);
|
|
613
|
+
}
|
|
614
|
+
async compileBlobType(_def) {
|
|
615
|
+
return this.options.allowLegacyBlobs
|
|
616
|
+
? 'l.BlobRef | l.LegacyBlobRef'
|
|
617
|
+
: 'l.BlobRef';
|
|
618
|
+
}
|
|
619
|
+
async compileCidLinkSchema(_def) {
|
|
620
|
+
return this.pure(`l.cid()`);
|
|
621
|
+
}
|
|
622
|
+
async compileCidLinkType(_def) {
|
|
623
|
+
return 'l.Cid';
|
|
624
|
+
}
|
|
625
|
+
async compileRefSchema(def) {
|
|
626
|
+
const { varName, typeName } = await this.refResolver.resolve(def.ref);
|
|
627
|
+
// @NOTE "as any" is needed in schemas with circular refs as TypeScript
|
|
628
|
+
// cannot infer the type of a value that depends on its initializer type
|
|
629
|
+
return this.pure(`l.ref<${typeName}>((() => ${varName}) as any)`);
|
|
630
|
+
}
|
|
631
|
+
async compileRefType(def) {
|
|
632
|
+
const ref = await this.refResolver.resolve(def.ref);
|
|
633
|
+
return ref.typeName;
|
|
634
|
+
}
|
|
635
|
+
async compileRefUnionSchema(def) {
|
|
636
|
+
if (def.refs.length === 0 && def.closed) {
|
|
637
|
+
return this.pure(`l.never()`);
|
|
638
|
+
}
|
|
639
|
+
const refs = await Promise.all(def.refs.map(async (ref) => {
|
|
640
|
+
const { varName, typeName } = await this.refResolver.resolve(ref);
|
|
641
|
+
// @NOTE "as any" is needed in schemas with circular refs as TypeScript
|
|
642
|
+
// cannot infer the type of a value that depends on its initializer type
|
|
643
|
+
return this.pure(`l.typedRef<${typeName}>((() => ${varName}) as any)`);
|
|
644
|
+
}));
|
|
645
|
+
return this.pure(`l.typedUnion([${refs.join(',')}], ${def.closed ?? false})`);
|
|
646
|
+
}
|
|
647
|
+
async compileRefUnionType(def) {
|
|
648
|
+
const types = await Promise.all(def.refs.map(async (ref) => {
|
|
649
|
+
const { typeName } = await this.refResolver.resolve(ref);
|
|
650
|
+
return `l.$Typed<${typeName}>`;
|
|
651
|
+
}));
|
|
652
|
+
if (!def.closed)
|
|
653
|
+
types.push('l.Unknown$TypedObject');
|
|
654
|
+
return types.join(' | ') || 'never';
|
|
655
|
+
}
|
|
656
|
+
async compileConstSchema(def) {
|
|
657
|
+
if (hasEnum(def) && !def.enum.includes(def.const)) {
|
|
658
|
+
return this.pure(`l.never()`);
|
|
659
|
+
}
|
|
660
|
+
const result = this.pure(`l.literal(${JSON.stringify(def.const)})`);
|
|
661
|
+
return this.withDefault(result, def.default);
|
|
662
|
+
}
|
|
663
|
+
async compileConstType(def) {
|
|
664
|
+
if (hasEnum(def) && !def.enum.includes(def.const)) {
|
|
665
|
+
return 'never';
|
|
666
|
+
}
|
|
667
|
+
return JSON.stringify(def.const);
|
|
668
|
+
}
|
|
669
|
+
async compileEnumSchema(def) {
|
|
670
|
+
if (def.enum.length === 0) {
|
|
671
|
+
return this.pure(`l.never()`);
|
|
672
|
+
}
|
|
673
|
+
const result = def.enum.length === 1
|
|
674
|
+
? this.pure(`l.literal(${JSON.stringify(def.enum[0])})`)
|
|
675
|
+
: this.pure(`l.enum(${JSON.stringify(def.enum)})`);
|
|
676
|
+
return this.withDefault(result, def.default);
|
|
677
|
+
}
|
|
678
|
+
async compileEnumType(def) {
|
|
679
|
+
return def.enum.map((v) => JSON.stringify(v)).join(' | ') || 'never';
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
exports.LexDefBuilder = LexDefBuilder;
|
|
683
|
+
function parseDescription(description) {
|
|
684
|
+
if (/deprecated/i.test(description)) {
|
|
685
|
+
const deprecationMatch = description.match(/(\s*deprecated\s*(?:--?|:)?\s*([^-]*)(?:-+)?)/i);
|
|
686
|
+
if (deprecationMatch) {
|
|
687
|
+
const { 1: match, 2: deprecationNotice } = deprecationMatch;
|
|
688
|
+
return {
|
|
689
|
+
description: description.replace(match, '').trim() || undefined,
|
|
690
|
+
tags: [{ tagName: 'deprecated', text: deprecationNotice?.trim() }],
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
return {
|
|
695
|
+
description: description.trim() || undefined,
|
|
696
|
+
tags: [{ tagName: 'deprecated' }],
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
description: description.trim() || undefined,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
function compileLeadingTrivia(description) {
|
|
705
|
+
if (!description)
|
|
706
|
+
return undefined;
|
|
707
|
+
const parsed = parseDescription(description);
|
|
708
|
+
if (!parsed.description && !parsed.tags?.length)
|
|
709
|
+
return undefined;
|
|
710
|
+
const tags = parsed.tags
|
|
711
|
+
?.map(({ tagName, text }) => (text ? `@${tagName} ${text}` : `@${tagName}`))
|
|
712
|
+
?.join('\n');
|
|
713
|
+
const text = `\n${[parsed.description, tags].filter(Boolean).join('\n\n')}`;
|
|
714
|
+
return `\n\n/**${text.replaceAll('\n', '\n * ')}\n */\n`;
|
|
715
|
+
}
|
|
716
|
+
function compileDocs(description) {
|
|
717
|
+
if (!description)
|
|
718
|
+
return undefined;
|
|
719
|
+
return [parseDescription(description)];
|
|
720
|
+
}
|
|
721
|
+
function stringifyOptions(obj, include, exclude) {
|
|
722
|
+
const filtered = Object.entries(obj).filter(([k]) => (!include || include.includes(k)) && !exclude?.includes(k));
|
|
723
|
+
return filtered.length ? JSON.stringify(Object.fromEntries(filtered)) : '';
|
|
724
|
+
}
|
|
725
|
+
function hasConst(def) {
|
|
726
|
+
return def.const != null;
|
|
727
|
+
}
|
|
728
|
+
function hasEnum(def) {
|
|
729
|
+
return def.enum != null;
|
|
730
|
+
}
|
|
731
|
+
function markPure(v) {
|
|
732
|
+
return `/*#__PURE__*/ ${v}`;
|
|
733
|
+
}
|
|
734
|
+
//# sourceMappingURL=lex-def-builder.js.map
|