@atproto/lex-builder 0.1.1 → 0.1.3

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.
@@ -22,9 +22,6 @@ export class LexDefBuilder {
22
22
  this.doc = doc;
23
23
  this.refResolver = new RefResolver(doc, file, indexer, options);
24
24
  }
25
- pure(code) {
26
- return this.options.pureAnnotations ? markPure(code) : code;
27
- }
28
25
  async build() {
29
26
  this.file.addVariableStatement({
30
27
  declarationKind: VariableDeclarationKind.Const,
@@ -89,7 +86,7 @@ export class LexDefBuilder {
89
86
  async addPermissionSet(hash, def) {
90
87
  const permission = def.permissions.map((def) => {
91
88
  const options = stringifyOptions(def, undefined, ['resource', 'type']);
92
- return this.pure(`l.permission(${JSON.stringify(def.resource)}, ${options})`);
89
+ return markPure(`l.permission(${JSON.stringify(def.resource)}, ${options})`);
93
90
  });
94
91
  const options = stringifyOptions(def, [
95
92
  'title',
@@ -98,102 +95,134 @@ export class LexDefBuilder {
98
95
  'detail:lang',
99
96
  ]);
100
97
  await this.addSchema(hash, def, {
101
- schema: this.pure(`l.permissionSet($nsid, [${permission.join(',')}], ${options})`),
98
+ schema: markPure(`l.permissionSet($nsid, [${permission.join(',')}], ${options})`),
99
+ });
100
+ }
101
+ async addParameters(parameters) {
102
+ const varName = '$params';
103
+ this.addUtils({
104
+ [varName]: await this.compileParamsSchema(parameters),
105
+ });
106
+ // @TODO Build the types instead of using an inferred type.
107
+ this.file.addTypeAlias({
108
+ isExported: true,
109
+ name: '$Params',
110
+ type: `l.InferOutput<typeof ${varName}>`,
111
+ docs: compileDocs(parameters?.description),
112
+ });
113
+ return varName;
114
+ }
115
+ async addInput(input) {
116
+ const varName = '$input';
117
+ this.addUtils({
118
+ [varName]: await this.compilePayload(input),
119
+ });
120
+ // @TODO Build the types instead of using an inferred type.
121
+ this.file.addTypeAlias({
122
+ isExported: true,
123
+ name: '$Input<B = l.BinaryData>',
124
+ type: `l.InferPayload<typeof ${varName}, B>`,
125
+ docs: compileDocs(input?.description),
126
+ });
127
+ this.file.addTypeAlias({
128
+ isExported: true,
129
+ name: '$InputBody<B = l.BinaryData>',
130
+ type: `l.InferPayloadBody<typeof ${varName}, B>`,
131
+ docs: compileDocs(input?.description),
102
132
  });
133
+ return varName;
134
+ }
135
+ async addOutput(output) {
136
+ const varName = '$output';
137
+ this.addUtils({
138
+ [varName]: await this.compilePayload(output),
139
+ });
140
+ // @TODO Build the types instead of using an inferred type.
141
+ this.file.addTypeAlias({
142
+ isExported: true,
143
+ name: '$Output<B = l.BinaryData>',
144
+ type: `l.InferPayload<typeof ${varName}, B>`,
145
+ docs: compileDocs(output?.description),
146
+ });
147
+ this.file.addTypeAlias({
148
+ isExported: true,
149
+ name: '$OutputBody<B = l.BinaryData>',
150
+ type: `l.InferPayloadBody<typeof ${varName}, B>`,
151
+ docs: compileDocs(output?.description),
152
+ });
153
+ return varName;
154
+ }
155
+ async addMessage(message) {
156
+ const varName = '$message';
157
+ this.addUtils({
158
+ [varName]: await this.compileBodySchema(message?.schema),
159
+ });
160
+ // @TODO Build the types instead of using an inferred type.
161
+ this.file.addTypeAlias({
162
+ isExported: true,
163
+ name: '$Message',
164
+ type: `l.InferOutput<typeof ${varName}>`,
165
+ docs: compileDocs(message?.description),
166
+ });
167
+ return varName;
103
168
  }
104
169
  async addProcedure(hash, def) {
105
170
  if (hash !== 'main') {
106
171
  throw new Error(`Definition ${hash} cannot be of type ${def.type}`);
107
172
  }
108
- // @TODO Build the types instead of using an inferred type.
109
173
  // Declare each piece of the method as its own top-level exported const
110
- // *before* `main`, so consumers that import a single helper (e.g.
111
- // `$lxm`) only pull in that subtree rather than the whole procedure.
112
- // `main.parameters` etc. would otherwise pin `main` in the module graph.
174
+ // *before* `main`. This allows to export those pieces individually instead
175
+ // of "extracting" them from the "main" definition as below, which is bad
176
+ // for tree-shaking.
177
+ //
178
+ // export const $params = main.params`
179
+ const paramsVar = await this.addParameters(def.parameters);
180
+ const inputVar = await this.addInput(def.input);
181
+ const outputVar = await this.addOutput(def.output);
182
+ await this.addSchema(hash, def, {
183
+ schema: markPure(`l.procedure($nsid, ${paramsVar}, ${inputVar}, ${outputVar}${formatErrorsArg(await this.compileErrors(def.errors))})`),
184
+ });
113
185
  this.addUtils({
114
186
  $lxm: '$nsid',
115
- $params: await this.compileParamsSchema(def.parameters),
116
- $input: await this.compilePayload(def.input),
117
- $output: await this.compilePayload(def.output),
118
- });
119
- const ref = await this.addSchema(hash, def, {
120
- schema: this.pure(`l.procedure($nsid, $params, $input, $output${formatErrorsArg(await this.compileErrors(def.errors))})`),
121
187
  });
122
- this.addMethodTypeUtils(ref, def);
123
188
  }
124
189
  async addQuery(hash, def) {
125
190
  if (hash !== 'main') {
126
191
  throw new Error(`Definition ${hash} cannot be of type ${def.type}`);
127
192
  }
128
- // @TODO Build the types instead of using an inferred type.
193
+ // Declare each piece of the method as its own top-level exported const
194
+ // *before* `main`. This allows to export those pieces individually instead
195
+ // of "extracting" them from the "main" definition as below, which is bad
196
+ // for tree-shaking:
197
+ //
198
+ // export const $params = main.params
199
+ const paramsVar = await this.addParameters(def.parameters);
200
+ const outputVar = await this.addOutput(def.output);
201
+ await this.addSchema(hash, def, {
202
+ schema: markPure(`l.query($nsid, ${paramsVar}, ${outputVar}${formatErrorsArg(await this.compileErrors(def.errors))})`),
203
+ });
129
204
  this.addUtils({
130
205
  $lxm: '$nsid',
131
- $params: await this.compileParamsSchema(def.parameters),
132
- $output: await this.compilePayload(def.output),
133
206
  });
134
- const ref = await this.addSchema(hash, def, {
135
- schema: this.pure(`l.query($nsid, $params, $output${formatErrorsArg(await this.compileErrors(def.errors))})`),
136
- });
137
- this.addMethodTypeUtils(ref, def);
138
207
  }
139
208
  async addSubscription(hash, def) {
140
209
  if (hash !== 'main') {
141
210
  throw new Error(`Definition ${hash} cannot be of type ${def.type}`);
142
211
  }
143
- // @TODO Build the types instead of using an inferred type.
212
+ // Declare each piece of the method as its own top-level exported const
213
+ // *before* `main`. This allows to export those pieces individually instead
214
+ // of "extracting" them from the "main" definition as below, which is bad
215
+ // for tree-shaking.
216
+ //
217
+ // export const $params = main.params`
218
+ const paramsVar = await this.addParameters(def.parameters);
219
+ const messageVar = await this.addMessage(def.message);
220
+ await this.addSchema(hash, def, {
221
+ schema: markPure(`l.subscription($nsid, ${paramsVar}, ${messageVar}${formatErrorsArg(await this.compileErrors(def.errors))})`),
222
+ });
144
223
  this.addUtils({
145
224
  $lxm: '$nsid',
146
- $params: await this.compileParamsSchema(def.parameters),
147
- $message: await this.compileBodySchema(def.message?.schema),
148
- });
149
- const ref = await this.addSchema(hash, def, {
150
- schema: this.pure(`l.subscription($nsid, $params, $message${formatErrorsArg(await this.compileErrors(def.errors))})`),
151
- });
152
- this.addMethodTypeUtils(ref, def);
153
- }
154
- addMethodTypeUtils(ref, def) {
155
- this.file.addTypeAlias({
156
- isExported: true,
157
- name: '$Params',
158
- type: `l.InferMethodParams<typeof ${ref.varName}>`,
159
- docs: compileDocs(def.parameters?.description),
160
225
  });
161
- if (def.type === 'procedure') {
162
- this.file.addTypeAlias({
163
- isExported: true,
164
- name: '$Input<B = l.BinaryData>',
165
- type: `l.InferMethodInput<typeof ${ref.varName}, B>`,
166
- docs: compileDocs(def.input?.description),
167
- });
168
- this.file.addTypeAlias({
169
- isExported: true,
170
- name: '$InputBody<B = l.BinaryData>',
171
- type: `l.InferMethodInputBody<typeof ${ref.varName}, B>`,
172
- docs: compileDocs(def.input?.description),
173
- });
174
- }
175
- if (def.type === 'procedure' || def.type === 'query') {
176
- this.file.addTypeAlias({
177
- isExported: true,
178
- name: '$Output<B = l.BinaryData>',
179
- type: `l.InferMethodOutput<typeof ${ref.varName}, B>`,
180
- docs: compileDocs(def.output?.description),
181
- });
182
- this.file.addTypeAlias({
183
- isExported: true,
184
- name: '$OutputBody<B = l.BinaryData>',
185
- type: `l.InferMethodOutputBody<typeof ${ref.varName}, B>`,
186
- docs: compileDocs(def.output?.description),
187
- });
188
- }
189
- if (def.type === 'subscription') {
190
- this.file.addTypeAlias({
191
- isExported: true,
192
- name: '$Message',
193
- type: `l.InferSubscriptionMessage<typeof ${ref.varName}>`,
194
- docs: compileDocs(def.message?.description),
195
- });
196
- }
197
226
  }
198
227
  async addRecord(hash, def) {
199
228
  if (hash !== 'main') {
@@ -205,7 +234,7 @@ export class LexDefBuilder {
205
234
  properties.unshift(`$type: ${JSON.stringify(l.$type(this.doc.id, hash))}`);
206
235
  await this.addSchema(hash, def, {
207
236
  type: `{ ${properties.join(';')} }`,
208
- schema: (ref) => this.pure(`l.record<${key}, ${ref.typeName}>(${key}, $nsid, ${objectSchema})`),
237
+ schema: (ref) => markPure(`l.record<${key}, ${ref.typeName}>(${key}, $nsid, ${objectSchema})`),
209
238
  objectUtils: true,
210
239
  validationUtils: true,
211
240
  });
@@ -216,14 +245,14 @@ export class LexDefBuilder {
216
245
  properties.unshift(`$type?: ${JSON.stringify(l.$type(this.doc.id, hash))}`);
217
246
  await this.addSchema(hash, def, {
218
247
  type: `{ ${properties.join(';')} }`,
219
- schema: (ref) => this.pure(`l.typedObject<${ref.typeName}>($nsid, ${JSON.stringify(hash)}, ${objectSchema})`),
248
+ schema: (ref) => markPure(`l.typedObject<${ref.typeName}>($nsid, ${JSON.stringify(hash)}, ${objectSchema})`),
220
249
  objectUtils: true,
221
250
  validationUtils: true,
222
251
  });
223
252
  }
224
253
  async addToken(hash, def) {
225
254
  await this.addSchema(hash, def, {
226
- schema: this.pure(`l.token($nsid, ${JSON.stringify(hash)})`),
255
+ schema: markPure(`l.token($nsid, ${JSON.stringify(hash)})`),
227
256
  type: JSON.stringify(l.$type(this.doc.id, hash)),
228
257
  validationUtils: true,
229
258
  });
@@ -241,7 +270,7 @@ export class LexDefBuilder {
241
270
  type: `(${await this.compileContainedType(def.items)})[]`,
242
271
  // @NOTE Not using compileArraySchema to allow specifying the generic
243
272
  // parameter to l.array<>.
244
- schema: (ref) => this.pure(`l.array<${ref.typeName}[number]>(${itemSchema}, ${options})`),
273
+ schema: (ref) => markPure(`l.array<${ref.typeName}[number]>(${itemSchema}, ${options})`),
245
274
  validationUtils: true,
246
275
  });
247
276
  }
@@ -290,9 +319,9 @@ export class LexDefBuilder {
290
319
  }
291
320
  if (hash === 'main' && objectUtils) {
292
321
  this.addUtils({
322
+ $type: `$nsid`,
293
323
  $isTypeOf: markPure(`${ref.varName}.isTypeOf.bind(${ref.varName})`),
294
324
  $build: markPure(`${ref.varName}.build.bind(${ref.varName})`),
295
- $type: `${ref.varName}.$type`,
296
325
  });
297
326
  }
298
327
  if (hash === 'main' && validationUtils) {
@@ -312,19 +341,19 @@ export class LexDefBuilder {
312
341
  }
313
342
  async compilePayload(def) {
314
343
  if (!def)
315
- return this.pure(`l.payload()`);
344
+ return markPure(`l.payload()`);
316
345
  // Special case for JSON object payloads
317
346
  if (def.encoding === 'application/json' && def.schema?.type === 'object') {
318
347
  const properties = await this.compilePropertiesSchemas(def.schema);
319
- return this.pure(`l.jsonPayload({${properties.join(',')}})`);
348
+ return markPure(`l.jsonPayload({${properties.join(',')}})`);
320
349
  }
321
350
  const encodedEncoding = JSON.stringify(def.encoding);
322
351
  if (def.schema) {
323
352
  const bodySchema = await this.compileBodySchema(def.schema);
324
- return this.pure(`l.payload(${encodedEncoding}, ${bodySchema})`);
353
+ return markPure(`l.payload(${encodedEncoding}, ${bodySchema})`);
325
354
  }
326
355
  else {
327
- return this.pure(`l.payload(${encodedEncoding})`);
356
+ return markPure(`l.payload(${encodedEncoding})`);
328
357
  }
329
358
  }
330
359
  async compileBodySchema(def) {
@@ -336,9 +365,9 @@ export class LexDefBuilder {
336
365
  }
337
366
  async compileParamsSchema(def) {
338
367
  if (!def)
339
- return this.pure(`l.params()`);
368
+ return markPure(`l.params()`);
340
369
  const properties = await this.compilePropertiesSchemas(def);
341
- return this.pure(properties.length === 0
370
+ return markPure(properties.length === 0
342
371
  ? `l.params()`
343
372
  : `l.params({${properties.join(',')}})`);
344
373
  }
@@ -349,7 +378,7 @@ export class LexDefBuilder {
349
378
  }
350
379
  async compileObjectSchema(def) {
351
380
  const properties = await this.compilePropertiesSchemas(def);
352
- return this.pure(`l.object({${properties.join(',')}})`);
381
+ return markPure(`l.object({${properties.join(',')}})`);
353
382
  }
354
383
  async compilePropertiesSchemas(options) {
355
384
  for (const opt of ['required', 'nullable']) {
@@ -375,10 +404,10 @@ export class LexDefBuilder {
375
404
  const isRequired = options.required?.includes(key);
376
405
  let schema = await this.compileContainedSchema(def);
377
406
  if (isNullable) {
378
- schema = this.pure(`l.nullable(${schema})`);
407
+ schema = markPure(`l.nullable(${schema})`);
379
408
  }
380
409
  if (!isRequired) {
381
- schema = this.pure(`l.optional(${schema})`);
410
+ schema = markPure(`l.optional(${schema})`);
382
411
  }
383
412
  return `${JSON.stringify(key)}:${schema}`;
384
413
  }
@@ -452,13 +481,13 @@ export class LexDefBuilder {
452
481
  'minLength',
453
482
  'maxLength',
454
483
  ]);
455
- return this.pure(`l.array(${itemSchema}, ${options})`);
484
+ return markPure(`l.array(${itemSchema}, ${options})`);
456
485
  }
457
486
  async compileArrayType(def) {
458
487
  return `(${await this.compileContainedType(def.items)})[]`;
459
488
  }
460
489
  async compileUnknownSchema(_def) {
461
- return this.pure(`l.lexMap()`);
490
+ return markPure(`l.lexMap()`);
462
491
  }
463
492
  async compileUnknownType(_def) {
464
493
  return `l.LexMap`;
@@ -466,7 +495,7 @@ export class LexDefBuilder {
466
495
  withDefault(schema, defaultValue) {
467
496
  if (defaultValue === undefined)
468
497
  return schema;
469
- return this.pure(`l.withDefault(${schema}, ${JSON.stringify(defaultValue)})`);
498
+ return markPure(`l.withDefault(${schema}, ${JSON.stringify(defaultValue)})`);
470
499
  }
471
500
  async compileBooleanSchema(def) {
472
501
  const schema = l.boolean();
@@ -475,7 +504,7 @@ export class LexDefBuilder {
475
504
  }
476
505
  if (hasConst(def))
477
506
  return this.compileConstSchema(def);
478
- return this.withDefault(this.pure(`l.boolean()`), def.default);
507
+ return this.withDefault(markPure(`l.boolean()`), def.default);
479
508
  }
480
509
  async compileBooleanType(def) {
481
510
  if (hasConst(def))
@@ -502,7 +531,7 @@ export class LexDefBuilder {
502
531
  'maximum',
503
532
  'minimum',
504
533
  ]);
505
- return this.withDefault(this.pure(`l.integer(${options})`), def.default);
534
+ return this.withDefault(markPure(`l.integer(${options})`), def.default);
506
535
  }
507
536
  async compileIntegerType(def) {
508
537
  if (hasConst(def))
@@ -547,7 +576,7 @@ export class LexDefBuilder {
547
576
  'knownValues',
548
577
  ])
549
578
  : undefined;
550
- return this.withDefault(this.pure(`l.string${generic ? `<${generic}>` : ''}(${options})`), def.default);
579
+ return this.withDefault(markPure(`l.string${generic ? `<${generic}>` : ''}(${options})`), def.default);
551
580
  }
552
581
  async compileStringType(def) {
553
582
  if (hasConst(def))
@@ -593,7 +622,7 @@ export class LexDefBuilder {
593
622
  'minLength',
594
623
  'maxLength',
595
624
  ]);
596
- return this.pure(`l.bytes(${options})`);
625
+ return markPure(`l.bytes(${options})`);
597
626
  }
598
627
  async compileBytesType(_def) {
599
628
  return 'Uint8Array';
@@ -603,13 +632,13 @@ export class LexDefBuilder {
603
632
  'maxSize',
604
633
  'accept',
605
634
  ]);
606
- return this.pure(`l.blob(${options})`);
635
+ return markPure(`l.blob(${options})`);
607
636
  }
608
637
  async compileBlobType(_def) {
609
638
  return 'l.BlobRef';
610
639
  }
611
640
  async compileCidLinkSchema(_def) {
612
- return this.pure(`l.cid()`);
641
+ return markPure(`l.cid()`);
613
642
  }
614
643
  async compileCidLinkType(_def) {
615
644
  return 'l.Cid';
@@ -618,7 +647,7 @@ export class LexDefBuilder {
618
647
  const { varName, typeName } = await this.refResolver.resolve(def.ref);
619
648
  // @NOTE "as any" is needed in schemas with circular refs as TypeScript
620
649
  // cannot infer the type of a value that depends on its initializer type
621
- return this.pure(`l.ref<${typeName}>((() => ${varName}) as any)`);
650
+ return markPure(`l.ref<${typeName}>((() => ${varName}) as any)`);
622
651
  }
623
652
  async compileRefType(def) {
624
653
  const ref = await this.refResolver.resolve(def.ref);
@@ -626,15 +655,15 @@ export class LexDefBuilder {
626
655
  }
627
656
  async compileRefUnionSchema(def) {
628
657
  if (def.refs.length === 0 && def.closed) {
629
- return this.pure(`l.never()`);
658
+ return markPure(`l.never()`);
630
659
  }
631
660
  const refs = await Promise.all(def.refs.map(async (ref) => {
632
661
  const { varName, typeName } = await this.refResolver.resolve(ref);
633
662
  // @NOTE "as any" is needed in schemas with circular refs as TypeScript
634
663
  // cannot infer the type of a value that depends on its initializer type
635
- return this.pure(`l.typedRef<${typeName}>((() => ${varName}) as any)`);
664
+ return markPure(`l.typedRef<${typeName}>((() => ${varName}) as any)`);
636
665
  }));
637
- return this.pure(`l.typedUnion([${refs.join(',')}], ${def.closed ?? false})`);
666
+ return markPure(`l.typedUnion([${refs.join(',')}], ${def.closed ?? false})`);
638
667
  }
639
668
  async compileRefUnionType(def) {
640
669
  const types = await Promise.all(def.refs.map(async (ref) => {
@@ -647,9 +676,9 @@ export class LexDefBuilder {
647
676
  }
648
677
  async compileConstSchema(def) {
649
678
  if (hasEnum(def) && !def.enum.includes(def.const)) {
650
- return this.pure(`l.never()`);
679
+ return markPure(`l.never()`);
651
680
  }
652
- const result = this.pure(`l.literal(${JSON.stringify(def.const)})`);
681
+ const result = markPure(`l.literal(${JSON.stringify(def.const)})`);
653
682
  return this.withDefault(result, def.default);
654
683
  }
655
684
  async compileConstType(def) {
@@ -660,11 +689,11 @@ export class LexDefBuilder {
660
689
  }
661
690
  async compileEnumSchema(def) {
662
691
  if (def.enum.length === 0) {
663
- return this.pure(`l.never()`);
692
+ return markPure(`l.never()`);
664
693
  }
665
694
  const result = def.enum.length === 1
666
- ? this.pure(`l.literal(${JSON.stringify(def.enum[0])})`)
667
- : this.pure(`l.enum(${JSON.stringify(def.enum)})`);
695
+ ? markPure(`l.literal(${JSON.stringify(def.enum[0])})`)
696
+ : markPure(`l.enum(${JSON.stringify(def.enum)})`);
668
697
  return this.withDefault(result, def.default);
669
698
  }
670
699
  async compileEnumType(def) {