···55dist/
66tsp-output/
77generated/
88-lexicons/
98*.tsbuildinfo
1091110# Test outputs
+13
CHANGELOG.md
···11+### 0.3.1
22+33+- Escape reserved keywords when generating code
44+55+### 0.3.0
66+77+- New package `@typelex/cli`
88+- See new recommended workflow on https://typelex.org/#install
99+1010+### 0.2.0
1111+1212+- Add `@external` support
1313+114### 0.1.6
215316- Rebuild
+165-31
DOCS.md
···258258259259The `@external` decorator tells the emitter to skip JSON output for that namespace. This is useful when referencing definitions from other Lexicons that you don't want to re-emit.
260260261261-You could collect external stubs in one file and import them:
262262-263263-```typescript
264264-import "@typelex/emitter";
265265-import "../atproto-stubs.tsp";
266266-267267-namespace app.bsky.actor.profile {
268268- model Main {
269269- labels?: (com.atproto.label.defs.SelfLabels | unknown);
270270- }
271271-}
272272-```
273273-274274-Then in `atproto-stubs.tsp`:
275275-276276-```typescript
277277-import "@typelex/emitter";
278278-279279-@external
280280-namespace com.atproto.label.defs {
281281- model SelfLabels { }
282282-}
283283-284284-@external
285285-namespace com.atproto.repo.defs {
286286- model StrongRef { }
287287- @token model SomeToken { } // Note: Tokens still need @token
288288-}
289289-// ... more stubs
290290-```
261261+Starting with 0.3.0, typelex will automatically generate a `typelex/externals.tsp` file based on the JSON files in your `lexicons/` folder, and enforce that it's imported into your `typelex/main.tsp` entry point. However, this will *not* include Lexicons from your app's namespace, but only external ones.
291262292263You'll want to ensure the real JSON for external Lexicons is available before running codegen.
293264···340311```
341312342313Note that `Caption` won't exist as a separate defโthe abstraction is erased in the output.
314314+315315+### Scalars
316316+317317+TypeSpec scalars let you create named types with constraints. **By default, scalars create standalone defs** (like models):
318318+319319+```typescript
320320+import "@typelex/emitter";
321321+322322+namespace com.example {
323323+ model Main {
324324+ handle?: Handle;
325325+ bio?: Bio;
326326+ }
327327+328328+ @maxLength(50)
329329+ scalar Handle extends string;
330330+331331+ @maxLength(256)
332332+ @maxGraphemes(128)
333333+ scalar Bio extends string;
334334+}
335335+```
336336+337337+This creates three defs: `main`, `handle`, and `bio`:
338338+339339+```json
340340+{
341341+ "id": "com.example",
342342+ "defs": {
343343+ "main": {
344344+ "type": "object",
345345+ "properties": {
346346+ "handle": { "type": "ref", "ref": "#handle" },
347347+ "bio": { "type": "ref", "ref": "#bio" }
348348+ }
349349+ },
350350+ "handle": {
351351+ "type": "string",
352352+ "maxLength": 50
353353+ },
354354+ "bio": {
355355+ "type": "string",
356356+ "maxLength": 256,
357357+ "maxGraphemes": 128
358358+ }
359359+ }
360360+}
361361+```
362362+363363+Use `@inline` to expand a scalar inline instead:
364364+365365+```typescript
366366+import "@typelex/emitter";
367367+368368+namespace com.example {
369369+ model Main {
370370+ handle?: Handle;
371371+ }
372372+373373+ @inline
374374+ @maxLength(50)
375375+ scalar Handle extends string;
376376+}
377377+```
378378+379379+Now `Handle` is expanded inline (no separate def):
380380+381381+```json
382382+// ...
383383+"properties": {
384384+ "handle": { "type": "string", "maxLength": 50 }
385385+}
386386+// ...
387387+```
343388344389## Top-Level Lexicon Types
345390···934979935980## Defaults and Constants
936981937937-### Defaults
982982+### Property Defaults
983983+984984+You can set default values on properties:
938985939986```typescript
940987import "@typelex/emitter";
···948995```
949996950997Maps to: `{"default": 1}`, `{"default": "en"}`
998998+999999+### Type Defaults
10001000+10011001+You can also set defaults on scalar and union types using the `@default` decorator:
10021002+10031003+```typescript
10041004+import "@typelex/emitter";
10051005+10061006+namespace com.example {
10071007+ model Main {
10081008+ mode?: Mode;
10091009+ priority?: Priority;
10101010+ }
10111011+10121012+ @default("standard")
10131013+ scalar Mode extends string;
10141014+10151015+ @default(1)
10161016+ @closed
10171017+ @inline
10181018+ union Priority { 1, 2, 3 }
10191019+}
10201020+```
10211021+10221022+This creates a default on the type definition itself:
10231023+10241024+```json
10251025+{
10261026+ "defs": {
10271027+ "mode": {
10281028+ "type": "string",
10291029+ "default": "standard"
10301030+ }
10311031+ }
10321032+}
10331033+```
10341034+10351035+For unions with token references, pass the model directly:
10361036+10371037+```typescript
10381038+import "@typelex/emitter";
10391039+10401040+namespace com.example {
10411041+ model Main {
10421042+ eventType?: EventType;
10431043+ }
10441044+10451045+ @default(InPerson)
10461046+ union EventType { Hybrid, InPerson, Virtual, string }
10471047+10481048+ @token model Hybrid {}
10491049+ @token model InPerson {}
10501050+ @token model Virtual {}
10511051+}
10521052+```
10531053+10541054+This resolves to the fully-qualified token NSID:
10551055+10561056+```json
10571057+{
10581058+ "eventType": {
10591059+ "type": "string",
10601060+ "knownValues": [
10611061+ "com.example#hybrid",
10621062+ "com.example#inPerson",
10631063+ "com.example#virtual"
10641064+ ],
10651065+ "default": "com.example#inPerson"
10661066+ }
10671067+}
10681068+```
10691069+10701070+**Important:** When a scalar or union creates a standalone def (not `@inline`), property-level defaults must match the type's `@default`. Otherwise you'll get an error:
10711071+10721072+```typescript
10731073+@default("standard")
10741074+scalar Mode extends string;
10751075+10761076+model Main {
10771077+ mode?: Mode = "custom"; // ERROR: Conflicting defaults!
10781078+}
10791079+```
10801080+10811081+Solutions:
10821082+1. Make the defaults match: `mode?: Mode = "standard"`
10831083+2. Mark the type `@inline`: Allows property-level defaults
10841084+3. Remove the property default: Uses the type's default
95110859521086### Constants
9531087
+24
LICENSE.md
···1818AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2020OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2121+SOFTWARE.
2222+2323+Contains lexicons from https://github.com/lexicon-community/lexicon under the following license:
2424+2525+MIT License
2626+2727+Copyright (c) 2024 Lexicon Community
2828+2929+Permission is hereby granted, free of charge, to any person obtaining a copy
3030+of this software and associated documentation files (the "Software"), to deal
3131+in the Software without restriction, including without limitation the rights
3232+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
3333+copies of the Software, and to permit persons to whom the Software is
3434+furnished to do so, subject to the following conditions:
3535+3636+The above copyright notice and this permission notice shall be included in all
3737+copies or substantial portions of the Software.
3838+3939+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
4040+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
4141+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
4242+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
4343+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
4444+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2145SOFTWARE.
···11+import "@typelex/emitter";
22+33+// Generated by typelex from ./lexicons (excluding com.test.*)
44+// This file is auto-generated. Do not edit manually.
55+66+@external
77+namespace com.atproto.label.defs {
88+ model SelfLabel { }
99+ model SelfLabels { }
1010+}
···11+import "@typelex/emitter";
22+33+// Generated by typelex from ./lexicons (excluding com.myapp.*)
44+// This file is auto-generated. Do not edit manually.
55+66+@external
77+namespace com.external.media.defs {
88+ model Video { }
99+}
···11+import "@typelex/emitter";
22+33+// Generated by typelex from ../lexicons (excluding com.myapp.*)
44+// This file is auto-generated. Do not edit manually.
55+66+@external
77+namespace com.atproto.label.defs {
88+ model SelfLabel { }
99+ model SelfLabels { }
1010+}
···11+import "@typelex/emitter";
22+33+// Generated by typelex from ../lexicons (excluding com.myapp.*)
44+// This file is auto-generated. Do not edit manually.
55+66+@external
77+namespace com.atproto.label.defs {
88+ model SelfLabel { }
99+ model SelfLabels { }
1010+}
···11+import "@typelex/emitter";
22+33+// Generated by typelex from ./lexicons (excluding pub.leaflet.*)
44+// This file is auto-generated. Do not edit manually.
55+66+@external
77+namespace app.bsky.feed.post.`record` {
88+ model Main { }
99+}
1010+1111+@external
1212+namespace com.atproto.server.defs {
1313+ model InviteCode { }
1414+}
···11+import { expect } from "vitest";
22+33+export async function run(project) {
44+ // Test: Namespace must end with .*
55+ let result = await project.runTypelex(["compile", "com.example"]);
66+ expect(result.exitCode).not.toBe(0);
77+ expect(result.output).toContain("namespace must end with .*");
88+99+ // Test: Output path must end with 'lexicons'
1010+ await project.writeFile("typelex/main.tsp", `import "@typelex/emitter";\nimport "./externals.tsp";\n`);
1111+ await project.writeFile("typelex/externals.tsp", `import "@typelex/emitter";\n`);
1212+1313+ result = await project.runTypelex(["compile", "com.test.*", "--out", "./output"]);
1414+ expect(result.exitCode).not.toBe(0);
1515+ expect(result.output).toContain("Output directory must end with 'lexicons'");
1616+1717+ // Test: main.tsp must exist
1818+ await project.runCommand("rm", ["-rf", "typelex"]);
1919+ result = await project.runTypelex(["compile", "com.test.*"]);
2020+ expect(result.exitCode).not.toBe(0);
2121+ expect(result.output).toContain("main.tsp not found");
2222+2323+ // Test: main.tsp first line must be import "@typelex/emitter"
2424+ await project.writeFile("typelex/main.tsp", `// wrong first line\nimport "./externals.tsp";\n`);
2525+ await project.writeFile("typelex/externals.tsp", `import "@typelex/emitter";\n`);
2626+ result = await project.runTypelex(["compile", "com.test.*"]);
2727+ expect(result.exitCode).not.toBe(0);
2828+ expect(result.output).toContain('main.tsp must start with: import "@typelex/emitter"');
2929+3030+ // Test: main.tsp second line must be import "./externals.tsp"
3131+ await project.writeFile("typelex/main.tsp", `import "@typelex/emitter";\n// wrong second line\n`);
3232+ result = await project.runTypelex(["compile", "com.test.*"]);
3333+ expect(result.exitCode).not.toBe(0);
3434+ expect(result.output).toContain('Line 2 of main.tsp must be: import "./externals.tsp"');
3535+}
···11+import "@typelex/emitter";
22+33+// Generated by typelex from ./lexicons (excluding com.myapp.*)
44+// This file is auto-generated. Do not edit manually.
55+66+@external
77+namespace com.atproto.label.defs {
88+ model SelfLabel { }
99+ model SelfLabels { }
1010+}
···11+import "@typelex/emitter";
22+33+// Generated by typelex from ./lexicons (excluding com.myapp.*)
44+// This file is auto-generated. Do not edit manually.
55+66+@external
77+namespace com.atproto.label.defs {
88+ model SelfLabel { }
99+ model SelfLabels { }
1010+}
···11+import "@typelex/emitter";
22+33+// Generated by typelex
44+// No external lexicons found
+10
packages/cli/vitest.config.ts
···11+import { defineConfig } from 'vitest/config';
22+33+export default defineConfig({
44+ test: {
55+ globals: true,
66+ environment: 'node',
77+ testTimeout: 60000, // CLI operations can take time
88+ hookTimeout: 60000,
99+ },
1010+});
+65
packages/emitter/lib/decorators.tsp
···163163extern dec errors(target: unknown, ...errors: unknown[]);
164164165165/**
166166+ * Forces a model, scalar, or union to be inlined instead of creating a standalone def.
167167+ * By default, named types create separate definitions with references.
168168+ * Use @inline to expand the type inline at each usage site.
169169+ *
170170+ * @example Inline model
171171+ * ```typespec
172172+ * @inline
173173+ * model Caption {
174174+ * text?: string;
175175+ * }
176176+ *
177177+ * model Main {
178178+ * captions?: Caption[]; // Expands inline, no separate "caption" def
179179+ * }
180180+ * ```
181181+ *
182182+ * @example Inline scalar
183183+ * ```typespec
184184+ * @inline
185185+ * @maxLength(50)
186186+ * scalar Handle extends string;
187187+ *
188188+ * model Main {
189189+ * handle?: Handle; // Expands to { type: "string", maxLength: 50 }
190190+ * }
191191+ * ```
192192+ *
193193+ * @example Inline union
194194+ * ```typespec
195195+ * @inline
196196+ * union Status { "active", "inactive", string }
197197+ *
198198+ * model Main {
199199+ * status?: Status; // Expands inline with knownValues
200200+ * }
201201+ * ```
202202+ */
203203+extern dec inline(target: unknown);
204204+205205+/**
206206+ * Specifies a default value for a scalar or union definition.
207207+ * Only valid on standalone scalar or union defs (not @inline).
208208+ * The value must match the underlying type (string, integer, or boolean).
209209+ * For unions with token refs, you can pass a model reference directly.
210210+ *
211211+ * @param value - The default value (literal or model reference for tokens)
212212+ *
213213+ * @example Scalar with default
214214+ * ```typespec
215215+ * @default("standard")
216216+ * scalar Mode extends string;
217217+ * ```
218218+ *
219219+ * @example Union with token default
220220+ * ```typespec
221221+ * @default(Inperson)
222222+ * union EventMode { Hybrid, Inperson, Virtual, string }
223223+ *
224224+ * @token
225225+ * model Inperson {}
226226+ * ```
227227+ */
228228+extern dec `default`(target: unknown, value: unknown);
229229+230230+/**
166231 * Marks a namespace as external, preventing it from emitting JSON output.
167232 * This decorator can only be applied to namespaces.
168233 * Useful for importing definitions from other lexicons without re-emitting them.
···2525const maxBytesKey = Symbol("maxBytes");
2626const minBytesKey = Symbol("minBytes");
2727const externalKey = Symbol("external");
2828+const defaultKey = Symbol("default");
28292930/**
3031 * @maxBytes decorator for maximum length of bytes type
···294295295296export function isReadOnly(program: Program, target: Type): boolean {
296297 return program.stateSet(readOnlyKey).has(target);
298298+}
299299+300300+/**
301301+ * @default decorator for setting default values on scalars and unions
302302+ * The value can be a literal (string, number, boolean) or a model reference for tokens
303303+ */
304304+export function $default(context: DecoratorContext, target: Type, value: any) {
305305+ // Just store the raw value - let the emitter handle unwrapping and validation
306306+ context.program.stateMap(defaultKey).set(target, value);
307307+}
308308+309309+export function getDefault(
310310+ program: Program,
311311+ target: Type,
312312+): any | undefined {
313313+ return program.stateMap(defaultKey).get(target);
297314}
298315299316/**
+286-22
packages/emitter/src/emitter.ts
···4848 LexCidLink,
4949 LexRefVariant,
5050 LexToken,
5151+ LexBoolean,
5252+ LexInteger,
5353+ LexString,
5154} from "./types.js";
52555356import {
···6871 getMaxBytes,
6972 getMinBytes,
7073 isExternal,
7474+ getDefault,
7175} from "./decorators.js";
72767377export interface EmitterOptions {
···97101 private program: Program,
98102 private options: EmitterOptions,
99103 ) {}
104104+105105+ /**
106106+ * Process the raw default value from the decorator, unwrapping TypeSpec value objects
107107+ * and returning either a primitive (string, number, boolean) or a Type (for model references)
108108+ */
109109+ private processDefaultValue(rawValue: any): string | number | boolean | Type | undefined {
110110+ if (rawValue === undefined) return undefined;
111111+112112+ // TypeSpec may wrap values - check if this is a value object first
113113+ if (rawValue && typeof rawValue === 'object' && rawValue.valueKind) {
114114+ if (rawValue.valueKind === "StringValue") {
115115+ return rawValue.value;
116116+ } else if (rawValue.valueKind === "NumericValue" || rawValue.valueKind === "NumberValue") {
117117+ return rawValue.value;
118118+ } else if (rawValue.valueKind === "BooleanValue") {
119119+ return rawValue.value;
120120+ }
121121+ return undefined; // Unsupported valueKind
122122+ }
123123+124124+ // Check if it's a Type object (Model, String, Number, Boolean literals)
125125+ if (rawValue && typeof rawValue === 'object' && rawValue.kind) {
126126+ if (rawValue.kind === "String") {
127127+ return (rawValue as StringLiteral).value;
128128+ } else if (rawValue.kind === "Number") {
129129+ return (rawValue as NumericLiteral).value;
130130+ } else if (rawValue.kind === "Boolean") {
131131+ return (rawValue as BooleanLiteral).value;
132132+ } else if (rawValue.kind === "Model") {
133133+ // Return the model itself for token references
134134+ return rawValue as Model;
135135+ }
136136+ return undefined; // Unsupported kind
137137+ }
138138+139139+ // Direct primitive value
140140+ if (typeof rawValue === 'string' || typeof rawValue === 'number' || typeof rawValue === 'boolean') {
141141+ return rawValue;
142142+ }
143143+144144+ return undefined;
145145+ }
100146101147 async emit() {
102148 const globalNs = this.program.getGlobalNamespaceType();
···356402 }
357403358404 private addScalarToDefs(lexicon: LexiconDoc, scalar: Scalar) {
405405+ // Only skip if the scalar itself is in TypeSpec namespace (built-in scalars)
359406 if (scalar.namespace?.name === "TypeSpec") return;
360360- if (scalar.baseScalar?.namespace?.name === "TypeSpec") return;
361407362408 // Skip @inline scalars - they should be inlined, not defined separately
363409 if (isInline(this.program, scalar)) {
···368414 const scalarDef = this.scalarToLexiconPrimitive(scalar, undefined);
369415 if (scalarDef) {
370416 const description = getDoc(this.program, scalar);
371371- lexicon.defs[defName] = { ...scalarDef, description } as LexUserType;
417417+418418+ // Apply @default decorator if present
419419+ const rawDefault = getDefault(this.program, scalar);
420420+ const defaultValue = this.processDefaultValue(rawDefault);
421421+ let defWithDefault: LexObjectProperty = { ...scalarDef };
422422+423423+ if (defaultValue !== undefined) {
424424+ // Check if it's a Type (model reference for tokens)
425425+ if (typeof defaultValue === 'object' && 'kind' in defaultValue) {
426426+ // For model references, we need to resolve to NSID
427427+ // This shouldn't happen for scalars, only unions support token refs
428428+ this.program.reportDiagnostic({
429429+ code: "invalid-default-on-scalar",
430430+ severity: "error",
431431+ message: "@default on scalars must be a literal value (string, number, or boolean), not a model reference",
432432+ target: scalar,
433433+ });
434434+ } else {
435435+ // Validate that the default value matches the type
436436+ this.assertValidValueForType(scalarDef.type, defaultValue, scalar);
437437+ // Type-safe narrowing based on both the type discriminator and value type
438438+ if (scalarDef.type === "boolean" && typeof defaultValue === "boolean") {
439439+ (defWithDefault as LexBoolean).default = defaultValue;
440440+ } else if (scalarDef.type === "integer" && typeof defaultValue === "number") {
441441+ (defWithDefault as LexInteger).default = defaultValue;
442442+ } else if (scalarDef.type === "string" && typeof defaultValue === "string") {
443443+ (defWithDefault as LexString).default = defaultValue;
444444+ }
445445+ }
446446+ }
447447+448448+ // Apply integer constraints for standalone scalar defs
449449+ if (scalarDef.type === "integer") {
450450+ const minValue = getMinValue(this.program, scalar);
451451+ if (minValue !== undefined) {
452452+ (defWithDefault as LexInteger).minimum = minValue;
453453+ }
454454+ const maxValue = getMaxValue(this.program, scalar);
455455+ if (maxValue !== undefined) {
456456+ (defWithDefault as LexInteger).maximum = maxValue;
457457+ }
458458+ }
459459+460460+ lexicon.defs[defName] = { ...defWithDefault, description } as LexUserType;
372461 }
373462 }
374463···391480 if (unionDef.type === "string" && (unionDef.knownValues || unionDef.enum)) {
392481 const defName = name.charAt(0).toLowerCase() + name.slice(1);
393482 const description = getDoc(this.program, union);
394394- lexicon.defs[defName] = { ...unionDef, description };
483483+484484+ // Apply @default decorator if present
485485+ const rawDefault = getDefault(this.program, union);
486486+ const defaultValue = this.processDefaultValue(rawDefault);
487487+ let defWithDefault: LexString = { ...unionDef as LexString };
488488+489489+ if (defaultValue !== undefined) {
490490+ // Check if it's a Type (model reference for tokens)
491491+ if (typeof defaultValue === 'object' && 'kind' in defaultValue) {
492492+ // Resolve the model reference to its NSID
493493+ const tokenModel = defaultValue as Model;
494494+ const tokenRef = this.getModelReference(tokenModel, true); // fullyQualified=true
495495+ if (tokenRef) {
496496+ defWithDefault = { ...defWithDefault, default: tokenRef };
497497+ } else {
498498+ this.program.reportDiagnostic({
499499+ code: "invalid-default-token",
500500+ severity: "error",
501501+ message: "@default value must be a valid token model reference",
502502+ target: union,
503503+ });
504504+ }
505505+ } else {
506506+ // Literal value - validate it matches the union type
507507+ if (typeof defaultValue !== "string") {
508508+ this.program.reportDiagnostic({
509509+ code: "invalid-default-value-type",
510510+ severity: "error",
511511+ message: `Default value type mismatch: expected string, got ${typeof defaultValue}`,
512512+ target: union,
513513+ });
514514+ } else {
515515+ defWithDefault = { ...defWithDefault, default: defaultValue };
516516+ }
517517+ }
518518+ }
519519+520520+ lexicon.defs[defName] = { ...defWithDefault, description };
395521 } else if (unionDef.type === "union") {
396522 this.program.reportDiagnostic({
397523 code: "union-refs-not-allowed-as-def",
···401527 `Use @inline to inline them at usage sites, use @token models for known values, or use string literals.`,
402528 target: union,
403529 });
530530+ } else if (unionDef.type === "integer" && (unionDef as LexInteger).enum) {
531531+ // Integer enums can also be defs
532532+ const defName = name.charAt(0).toLowerCase() + name.slice(1);
533533+ const description = getDoc(this.program, union);
534534+535535+ // Apply @default decorator if present
536536+ const rawDefault = getDefault(this.program, union);
537537+ const defaultValue = this.processDefaultValue(rawDefault);
538538+ let defWithDefault: LexInteger = { ...unionDef as LexInteger };
539539+540540+ if (defaultValue !== undefined) {
541541+ if (typeof defaultValue === "number") {
542542+ defWithDefault = { ...defWithDefault, default: defaultValue };
543543+ } else {
544544+ this.program.reportDiagnostic({
545545+ code: "invalid-default-value-type",
546546+ severity: "error",
547547+ message: `Default value type mismatch: expected integer, got ${typeof defaultValue}`,
548548+ target: union,
549549+ });
550550+ }
551551+ }
552552+553553+ lexicon.defs[defName] = { ...defWithDefault, description };
404554 }
405555 }
406556···501651 isClosed(this.program, unionType)
502652 ) {
503653 const propDesc = prop ? getDoc(this.program, prop) : undefined;
504504- const defaultValue = prop?.defaultValue
505505- ? serializeValueAsJson(this.program, prop.defaultValue, prop)
506506- : undefined;
654654+655655+ // Check for default value: property default takes precedence, then union's @default
656656+ let defaultValue: string | number | boolean | undefined;
657657+ if (prop?.defaultValue !== undefined) {
658658+ defaultValue = serializeValueAsJson(this.program, prop.defaultValue, prop) as string | number | boolean;
659659+ } else {
660660+ // If no property default, check union's @default decorator
661661+ const rawUnionDefault = getDefault(this.program, unionType);
662662+ const unionDefault = this.processDefaultValue(rawUnionDefault);
663663+ if (unionDefault !== undefined && typeof unionDefault === 'number') {
664664+ defaultValue = unionDefault;
665665+ }
666666+ }
667667+507668 return {
508669 type: "integer",
509670 enum: variants.numericLiterals,
···526687 ) {
527688 const isClosedUnion = isClosed(this.program, unionType);
528689 const propDesc = prop ? getDoc(this.program, prop) : undefined;
529529- const defaultValue = prop?.defaultValue
530530- ? serializeValueAsJson(this.program, prop.defaultValue, prop)
531531- : undefined;
690690+691691+ // Check for default value: property default takes precedence, then union's @default
692692+ let defaultValue: string | number | boolean | undefined;
693693+ if (prop?.defaultValue !== undefined) {
694694+ defaultValue = serializeValueAsJson(this.program, prop.defaultValue, prop) as string | number | boolean;
695695+ } else {
696696+ // If no property default, check union's @default decorator
697697+ const rawUnionDefault = getDefault(this.program, unionType);
698698+ const unionDefault = this.processDefaultValue(rawUnionDefault);
699699+700700+ if (unionDefault !== undefined) {
701701+ // Check if it's a Type (model reference for tokens)
702702+ if (typeof unionDefault === 'object' && 'kind' in unionDefault && unionDefault.kind === 'Model') {
703703+ // Resolve the model reference to its NSID
704704+ const tokenModel = unionDefault as Model;
705705+ const tokenRef = this.getModelReference(tokenModel, true); // fullyQualified=true
706706+ if (tokenRef) {
707707+ defaultValue = tokenRef;
708708+ }
709709+ } else if (typeof unionDefault === 'string') {
710710+ defaultValue = unionDefault;
711711+ }
712712+ }
713713+ }
714714+532715 const maxLength = getMaxLength(this.program, unionType);
533716 const minLength = getMinLength(this.program, unionType);
534717 const maxGraphemes = getMaxGraphemes(this.program, unionType);
···11581341 prop?: ModelProperty,
11591342 propDesc?: string,
11601343 ): LexObjectProperty | null {
13441344+ // Check if this scalar should be referenced instead of inlined
13451345+ const scalarRef = this.getScalarReference(scalar);
13461346+ if (scalarRef) {
13471347+ // Check if property has a default value that would conflict with the scalar's @default
13481348+ if (prop?.defaultValue !== undefined) {
13491349+ const scalarDefaultRaw = getDefault(this.program, scalar);
13501350+ const scalarDefault = this.processDefaultValue(scalarDefaultRaw);
13511351+ const propDefault = serializeValueAsJson(this.program, prop.defaultValue, prop);
13521352+13531353+ // If the scalar has a different default, or if the property has a default but the scalar doesn't, error
13541354+ if (scalarDefault !== propDefault) {
13551355+ this.program.reportDiagnostic({
13561356+ code: "conflicting-defaults",
13571357+ severity: "error",
13581358+ message: scalarDefault !== undefined
13591359+ ? `Property default value conflicts with scalar's @default decorator. The scalar "${scalar.name}" has @default(${JSON.stringify(scalarDefault)}) but property has default value ${JSON.stringify(propDefault)}. Either remove the property default, mark the scalar @inline, or make the defaults match.`
13601360+ : `Property has a default value but the referenced scalar "${scalar.name}" does not. Either add @default to the scalar, mark it @inline to allow property-level defaults, or remove the property default.`,
13611361+ target: prop,
13621362+ });
13631363+ }
13641364+ }
13651365+13661366+ return { type: "ref" as const, ref: scalarRef, description: propDesc };
13671367+ }
13681368+13691369+ // Inline the scalar
11611370 const primitive = this.scalarToLexiconPrimitive(scalar, prop);
11621371 if (!primitive) return null;
11631372···12461455 if (!isDefining) {
12471456 const unionRef = this.getUnionReference(unionType);
12481457 if (unionRef) {
14581458+ // Check if property has a default value that would conflict with the union's @default
14591459+ if (prop?.defaultValue !== undefined) {
14601460+ const unionDefaultRaw = getDefault(this.program, unionType);
14611461+ const unionDefault = this.processDefaultValue(unionDefaultRaw);
14621462+ const propDefault = serializeValueAsJson(this.program, prop.defaultValue, prop);
14631463+14641464+ // For union defaults that are model references, we need to resolve them for comparison
14651465+ let resolvedUnionDefault: string | number | boolean | undefined;
14661466+ if (unionDefault && typeof unionDefault === 'object' && 'kind' in unionDefault && unionDefault.kind === 'Model') {
14671467+ const ref = this.getModelReference(unionDefault as Model, true);
14681468+ resolvedUnionDefault = ref || undefined;
14691469+ } else {
14701470+ resolvedUnionDefault = unionDefault as string | number | boolean;
14711471+ }
14721472+14731473+ // If the union has a different default, or if the property has a default but the union doesn't, error
14741474+ if (resolvedUnionDefault !== propDefault) {
14751475+ this.program.reportDiagnostic({
14761476+ code: "conflicting-defaults",
14771477+ severity: "error",
14781478+ message: unionDefault !== undefined
14791479+ ? `Property default value conflicts with union's @default decorator. The union "${unionType.name}" has @default(${JSON.stringify(resolvedUnionDefault)}) but property has default value ${JSON.stringify(propDefault)}. Either remove the property default, mark the union @inline, or make the defaults match.`
14801480+ : `Property has a default value but the referenced union "${unionType.name}" does not. Either add @default to the union, mark it @inline to allow property-level defaults, or remove the property default.`,
14811481+ target: prop,
14821482+ });
14831483+ }
14841484+ }
14851485+12491486 return { type: "ref" as const, ref: unionRef, description: propDesc };
12501487 }
12511488 }
···12711508 // Check if this scalar (or its base) is bytes type
12721509 if (this.isScalarOfType(scalar, "bytes")) {
12731510 const byteDef: LexBytes = { type: "bytes" };
12741274- const target = prop || scalar;
1275151112761276- const minLength = getMinBytes(this.program, target);
15121512+ // Check scalar first for its own constraints, then property overrides
15131513+ const minLength = getMinBytes(this.program, scalar) ?? (prop ? getMinBytes(this.program, prop) : undefined);
12771514 if (minLength !== undefined) {
12781515 byteDef.minLength = minLength;
12791516 }
1280151712811281- const maxLength = getMaxBytes(this.program, target);
15181518+ const maxLength = getMaxBytes(this.program, scalar) ?? (prop ? getMaxBytes(this.program, prop) : undefined);
12821519 if (maxLength !== undefined) {
12831520 byteDef.maxLength = maxLength;
12841521 }
···1310154713111548 // Apply string constraints
13121549 if (primitive.type === "string") {
13131313- const target = prop || scalar;
13141314- const maxLength = getMaxLength(this.program, target);
15501550+ // Check scalar first for its own constraints, then property overrides
15511551+ const maxLength = getMaxLength(this.program, scalar) ?? (prop ? getMaxLength(this.program, prop) : undefined);
13151552 if (maxLength !== undefined) {
13161553 primitive.maxLength = maxLength;
13171554 }
13181318- const minLength = getMinLength(this.program, target);
15551555+ const minLength = getMinLength(this.program, scalar) ?? (prop ? getMinLength(this.program, prop) : undefined);
13191556 if (minLength !== undefined) {
13201557 primitive.minLength = minLength;
13211558 }
13221322- const maxGraphemes = getMaxGraphemes(this.program, target);
15591559+ const maxGraphemes = getMaxGraphemes(this.program, scalar) ?? (prop ? getMaxGraphemes(this.program, prop) : undefined);
13231560 if (maxGraphemes !== undefined) {
13241561 primitive.maxGraphemes = maxGraphemes;
13251562 }
13261326- const minGraphemes = getMinGraphemes(this.program, target);
15631563+ const minGraphemes = getMinGraphemes(this.program, scalar) ?? (prop ? getMinGraphemes(this.program, prop) : undefined);
13271564 if (minGraphemes !== undefined) {
13281565 primitive.minGraphemes = minGraphemes;
13291566 }
13301567 }
1331156813321569 // Apply numeric constraints
13331333- if (prop && primitive.type === "integer") {
13341334- const minValue = getMinValue(this.program, prop);
15701570+ if (primitive.type === "integer") {
15711571+ // Check scalar first for its own constraints, then property overrides
15721572+ const minValue = getMinValue(this.program, scalar) ?? (prop ? getMinValue(this.program, prop) : undefined);
13351573 if (minValue !== undefined) {
13361574 primitive.minimum = minValue;
13371575 }
13381338- const maxValue = getMaxValue(this.program, prop);
15761576+ const maxValue = getMaxValue(this.program, scalar) ?? (prop ? getMaxValue(this.program, prop) : undefined);
13391577 if (maxValue !== undefined) {
13401578 primitive.maximum = maxValue;
13411579 }
···14311669 private assertValidValueForType(
14321670 primitiveType: string,
14331671 value: unknown,
14341434- prop: ModelProperty,
16721672+ target: ModelProperty | Scalar | Union,
14351673 ): void {
14361674 const valid =
14371675 (primitiveType === "boolean" && typeof value === "boolean") ||
···14421680 code: "invalid-default-value-type",
14431681 severity: "error",
14441682 message: `Default value type mismatch: expected ${primitiveType}, got ${typeof value}`,
14451445- target: prop,
16831683+ target: target,
14461684 });
14471685 }
14481686 }
···1507174515081746 private getUnionReference(union: Union): string | null {
15091747 return this.getReference(union, union.name, union.namespace);
17481748+ }
17491749+17501750+ private getScalarReference(scalar: Scalar): string | null {
17511751+ // Built-in TypeSpec scalars (string, integer, boolean themselves) should not be referenced
17521752+ if (scalar.namespace?.name === "TypeSpec") return null;
17531753+17541754+ // @inline scalars should be inlined, not referenced
17551755+ if (isInline(this.program, scalar)) return null;
17561756+17571757+ // Scalars without names or namespace can't be referenced
17581758+ if (!scalar.name || !scalar.namespace) return null;
17591759+17601760+ const defName = scalar.name.charAt(0).toLowerCase() + scalar.name.slice(1);
17611761+ const namespaceName = getNamespaceFullName(scalar.namespace);
17621762+ if (!namespaceName) return null;
17631763+17641764+ // Local reference (same namespace) - use short ref
17651765+ if (
17661766+ this.currentLexiconId === namespaceName ||
17671767+ this.currentLexiconId === `${namespaceName}.defs`
17681768+ ) {
17691769+ return `#${defName}`;
17701770+ }
17711771+17721772+ // Cross-namespace reference
17731773+ return `${namespaceName}#${defName}`;
15101774 }
1511177515121776 private modelToLexiconArray(
···11+import "@typelex/emitter";
22+33+namespace community.lexicon.bookmarks.bookmark {
44+ /** Record bookmarking a link to come back to later. */
55+ @rec("tid")
66+ model Main {
77+ @required subject: uri;
88+99+ @required createdAt: datetime;
1010+1111+ /** Tags for content the bookmark may be related to, for example 'news' or 'funny videos' */
1212+ tags?: string[];
1313+ }
1414+}
···11+import "@typelex/emitter";
22+33+namespace community.lexicon.bookmarks.getActorBookmarks {
44+ /** Get a list of bookmarks by actor. Optionally add a list of tags to include, default will be all bookmarks. Requires auth, actor must be the requesting account. */
55+ @query
66+ op main(
77+ tags?: string[],
88+99+ @minValue(1)
1010+ @maxValue(100)
1111+ limit?: int32 = 50,
1212+1313+ cursor?: string,
1414+ ): {
1515+ @required
1616+ bookmarks: community.lexicon.bookmarks.bookmark.Main[];
1717+1818+ cursor?: string;
1919+ };
2020+}
2121+2222+// --- Externals ---
2323+2424+@external
2525+namespace community.lexicon.bookmarks.bookmark {
2626+ model Main {}
2727+}
···11+import "@typelex/emitter";
22+33+namespace community.lexicon.calendar.event {
44+ /** A calendar event. */
55+ @rec("tid")
66+ model Main {
77+ /** The name of the event. */
88+ @required
99+ name: string;
1010+1111+ /** The description of the event. */
1212+ description?: string;
1313+1414+ /** Client-declared timestamp when the event was created. */
1515+ @required
1616+ createdAt: datetime;
1717+1818+ /** Client-declared timestamp when the event starts. */
1919+ startsAt?: datetime;
2020+2121+ /** Client-declared timestamp when the event ends. */
2222+ endsAt?: datetime;
2323+2424+ /** The attendance mode of the event. */
2525+ mode?: Mode;
2626+2727+ /** The status of the event. */
2828+ status?: Status;
2929+3030+ /** The locations where the event takes place. */
3131+ locations?: (
3232+ | Uri
3333+ | community.lexicon.location.address.Main
3434+ | community.lexicon.location.fsq.Main
3535+ | community.lexicon.location.geo.Main
3636+ | community.lexicon.location.hthree.Main
3737+ )[];
3838+3939+ /** URIs associated with the event. */
4040+ uris?: Uri[];
4141+ }
4242+4343+ /** The mode of the event. */
4444+ @default(Inperson)
4545+ union Mode {
4646+ Hybrid,
4747+ Inperson,
4848+ Virtual,
4949+ string,
5050+ }
5151+5252+ /** A virtual event that takes place online. */
5353+ @token
5454+ model Virtual {}
5555+5656+ /** An in-person event that takes place offline. */
5757+ @token
5858+ model Inperson {}
5959+6060+ /** A hybrid event that takes place both online and offline. */
6161+ @token
6262+ model Hybrid {}
6363+6464+ /** The status of the event. */
6565+ @default(Scheduled)
6666+ union Status {
6767+ Cancelled,
6868+ Planned,
6969+ Postponed,
7070+ Rescheduled,
7171+ Scheduled,
7272+ string,
7373+ }
7474+7575+ /** The event has been created, but not finalized. */
7676+ @token
7777+ model Planned {}
7878+7979+ /** The event has been created and scheduled. */
8080+ @token
8181+ model Scheduled {}
8282+8383+ /** The event has been rescheduled. */
8484+ @token
8585+ model Rescheduled {}
8686+8787+ /** The event has been cancelled. */
8888+ @token
8989+ model Cancelled {}
9090+9191+ /** The event has been postponed and a new start date has not been set. */
9292+ @token
9393+ model Postponed {}
9494+9595+ /** A URI associated with the event. */
9696+ model Uri {
9797+ @required
9898+ uri: uri;
9999+100100+ /** The display name of the URI. */
101101+ name?: string;
102102+ }
103103+}
104104+105105+// --- Externals ---
106106+107107+@external
108108+namespace community.lexicon.location.address {
109109+ model Main {}
110110+}
111111+112112+@external
113113+namespace community.lexicon.location.fsq {
114114+ model Main {}
115115+}
116116+117117+@external
118118+namespace community.lexicon.location.geo {
119119+ model Main {}
120120+}
121121+122122+@external
123123+namespace community.lexicon.location.hthree {
124124+ model Main {}
125125+}
···11+import "@typelex/emitter";
22+33+namespace community.lexicon.location.address {
44+ /** A physical location in the form of a street address. */
55+ model Main {
66+ /** The ISO 3166 country code. Preferably the 2-letter code. */
77+ @required
88+ @minLength(2)
99+ @maxLength(10)
1010+ country: string;
1111+1212+ /** The postal code of the location. */
1313+ postalCode?: string;
1414+1515+ /** The administrative region of the country. For example, a state in the USA. */
1616+ region?: string;
1717+1818+ /** The locality of the region. For example, a city in the USA. */
1919+ locality?: string;
2020+2121+ /** The street address. */
2222+ street?: string;
2323+2424+ /** The name of the location. */
2525+ name?: string;
2626+ }
2727+}
···11+import "@typelex/emitter";
22+33+namespace community.lexicon.location.fsq {
44+ /** A physical location contained in the Foursquare Open Source Places dataset. */
55+ model Main {
66+ /** The unique identifier of a Foursquare POI. */
77+ @required fsq_place_id: string;
88+99+ latitude?: string;
1010+ longitude?: string;
1111+1212+ /** The name of the location. */
1313+ name?: string;
1414+ }
1515+}
···11+import "@typelex/emitter";
22+33+namespace community.lexicon.location.geo {
44+ /** A physical location in the form of a WGS84 coordinate. */
55+ model Main {
66+ @required latitude: string;
77+ @required longitude: string;
88+ altitude?: string;
99+1010+ /** The name of the location. */
1111+ name?: string;
1212+ }
1313+}
···11+import "@typelex/emitter";
22+33+namespace community.lexicon.location.hthree {
44+ /** A physical location in the form of a H3 encoded location. */
55+ model Main {
66+ /** The h3 encoded location. */
77+ @required value: string;
88+99+ /** The name of the location. */
1010+ name?: string;
1111+ }
1212+}
···11+import "@typelex/emitter";
22+33+/** Web Monetization integration: https://webmonetization.org/ */
44+namespace community.lexicon.payments.webMonetization {
55+ /** Web Monetization wallet. */
66+ @rec("any")
77+ model Main {
88+ /** Wallet address. */
99+ @required address: uri;
1010+1111+ /** Short, human-readable description of how this wallet is related to this account. */
1212+ note?: string;
1313+ }
1414+}
···11+{
22+ "lexicon": 1,
33+ "id": "community.lexicon.location.address",
44+ "defs": {
55+ "main": {
66+ "type": "object",
77+ "description": "A physical location in the form of a street address.",
88+ "required": [
99+ "country"
1010+ ],
1111+ "properties": {
1212+ "country": {
1313+ "type": "string",
1414+ "description": "The ISO 3166 country code. Preferably the 2-letter code.",
1515+ "minLength": 2,
1616+ "maxLength": 10
1717+ },
1818+ "postalCode": {
1919+ "type": "string",
2020+ "description": "The postal code of the location."
2121+ },
2222+ "region": {
2323+ "type": "string",
2424+ "description": "The administrative region of the country. For example, a state in the USA."
2525+ },
2626+ "locality": {
2727+ "type": "string",
2828+ "description": "The locality of the region. For example, a city in the USA."
2929+ },
3030+ "street": {
3131+ "type": "string",
3232+ "description": "The street address."
3333+ },
3434+ "name": {
3535+ "type": "string",
3636+ "description": "The name of the location."
3737+ }
3838+ }
3939+ }
4040+ }
4141+}
···11+import "@typelex/emitter";
22+33+namespace com.example.scalarDefaults {
44+ /** Test default decorator on scalars */
55+ model Main {
66+ /** Uses string scalar with default */
77+ mode?: Mode;
88+99+ /** Uses integer scalar with default */
1010+ limit?: Limit;
1111+1212+ /** Uses boolean scalar with default */
1313+ enabled?: Enabled;
1414+ }
1515+1616+ /** A string type with a default value */
1717+ @default("standard")
1818+ @maxLength(50)
1919+ scalar Mode extends string;
2020+2121+ /** An integer type with a default value */
2222+ @default(50)
2323+ @minValue(1)
2424+ @maxValue(100)
2525+ scalar Limit extends integer;
2626+2727+ /** A boolean type with a default value */
2828+ @default(true)
2929+ scalar Enabled extends boolean;
3030+}
···11+{
22+ "lexicon": 1,
33+ "id": "com.atproto.label.defs",
44+ "defs": {
55+ "label": {
66+ "type": "object",
77+ "description": "Metadata tag on an atproto resource (eg, repo or record).",
88+ "required": ["src", "uri", "val", "cts"],
99+ "properties": {
1010+ "ver": {
1111+ "type": "integer",
1212+ "description": "The AT Protocol version of the label object."
1313+ },
1414+ "src": {
1515+ "type": "string",
1616+ "format": "did",
1717+ "description": "DID of the actor who created this label."
1818+ },
1919+ "uri": {
2020+ "type": "string",
2121+ "format": "uri",
2222+ "description": "AT URI of the record, repository (account), or other resource that this label applies to."
2323+ },
2424+ "cid": {
2525+ "type": "string",
2626+ "format": "cid",
2727+ "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to."
2828+ },
2929+ "val": {
3030+ "type": "string",
3131+ "maxLength": 128,
3232+ "description": "The short string name of the value or type of this label."
3333+ },
3434+ "neg": {
3535+ "type": "boolean",
3636+ "description": "If true, this is a negation label, overwriting a previous label."
3737+ },
3838+ "cts": {
3939+ "type": "string",
4040+ "format": "datetime",
4141+ "description": "Timestamp when this label was created."
4242+ },
4343+ "exp": {
4444+ "type": "string",
4545+ "format": "datetime",
4646+ "description": "Timestamp at which this label expires (no longer applies)."
4747+ },
4848+ "sig": {
4949+ "type": "bytes",
5050+ "description": "Signature of dag-cbor encoded label."
5151+ }
5252+ }
5353+ },
5454+ "selfLabels": {
5555+ "type": "object",
5656+ "description": "Metadata tags on an atproto record, published by the author within the record.",
5757+ "required": ["values"],
5858+ "properties": {
5959+ "values": {
6060+ "type": "array",
6161+ "items": { "type": "ref", "ref": "#selfLabel" },
6262+ "maxLength": 10
6363+ }
6464+ }
6565+ },
6666+ "selfLabel": {
6767+ "type": "object",
6868+ "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.",
6969+ "required": ["val"],
7070+ "properties": {
7171+ "val": {
7272+ "type": "string",
7373+ "maxLength": 128,
7474+ "description": "The short string name of the value or type of this label."
7575+ }
7676+ }
7777+ },
7878+ "labelValueDefinition": {
7979+ "type": "object",
8080+ "description": "Declares a label value and its expected interpretations and behaviors.",
8181+ "required": ["identifier", "severity", "blurs", "locales"],
8282+ "properties": {
8383+ "identifier": {
8484+ "type": "string",
8585+ "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).",
8686+ "maxLength": 100,
8787+ "maxGraphemes": 100
8888+ },
8989+ "severity": {
9090+ "type": "string",
9191+ "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.",
9292+ "knownValues": ["inform", "alert", "none"]
9393+ },
9494+ "blurs": {
9595+ "type": "string",
9696+ "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.",
9797+ "knownValues": ["content", "media", "none"]
9898+ },
9999+ "defaultSetting": {
100100+ "type": "string",
101101+ "description": "The default setting for this label.",
102102+ "knownValues": ["ignore", "warn", "hide"],
103103+ "default": "warn"
104104+ },
105105+ "adultOnly": {
106106+ "type": "boolean",
107107+ "description": "Does the user need to have adult content enabled in order to configure this label?"
108108+ },
109109+ "locales": {
110110+ "type": "array",
111111+ "items": { "type": "ref", "ref": "#labelValueDefinitionStrings" }
112112+ }
113113+ }
114114+ },
115115+ "labelValueDefinitionStrings": {
116116+ "type": "object",
117117+ "description": "Strings which describe the label in the UI, localized into a specific language.",
118118+ "required": ["lang", "name", "description"],
119119+ "properties": {
120120+ "lang": {
121121+ "type": "string",
122122+ "description": "The code of the language these strings are written in.",
123123+ "format": "language"
124124+ },
125125+ "name": {
126126+ "type": "string",
127127+ "description": "A short human-readable name for the label.",
128128+ "maxGraphemes": 64,
129129+ "maxLength": 640
130130+ },
131131+ "description": {
132132+ "type": "string",
133133+ "description": "A longer description of what the label means and why it might be applied.",
134134+ "maxGraphemes": 10000,
135135+ "maxLength": 100000
136136+ }
137137+ }
138138+ },
139139+ "labelValue": {
140140+ "type": "string",
141141+ "knownValues": [
142142+ "!hide",
143143+ "!no-promote",
144144+ "!warn",
145145+ "!no-unauthenticated",
146146+ "dmca-violation",
147147+ "doxxing",
148148+ "porn",
149149+ "sexual",
150150+ "nudity",
151151+ "nsfl",
152152+ "gore"
153153+ ]
154154+ }
155155+ }
156156+}
···11+{
22+ "lexicon": 1,
33+ "id": "com.atproto.repo.uploadBlob",
44+ "defs": {
55+ "main": {
66+ "type": "procedure",
77+ "description": "Upload a new blob, to be referenced from a repository record. The blob will be deleted if it is not referenced within a time window (eg, minutes). Blob restrictions (mimetype, size, etc) are enforced when the reference is created. Requires auth, implemented by PDS.",
88+ "input": {
99+ "encoding": "*/*"
1010+ },
1111+ "output": {
1212+ "encoding": "application/json",
1313+ "schema": {
1414+ "type": "object",
1515+ "required": ["blob"],
1616+ "properties": {
1717+ "blob": { "type": "blob" }
1818+ }
1919+ }
2020+ }
2121+ }
2222+ }
2323+}