An experimental TypeSpec syntax for Lexicon

more

+357
+12
packages/emitter/src/decorators.ts
··· 16 16 const errorKey = Symbol("error"); 17 17 const errorModelKey = Symbol("errorModel"); 18 18 const encodingKey = Symbol("encoding"); 19 + const inlineKey = Symbol("inline"); 19 20 20 21 /** 21 22 * @encoding decorator for custom output/input encoding ··· 236 237 export function isErrorModel(program: Program, target: Type): boolean { 237 238 return program.stateSet(errorModelKey).has(target); 238 239 } 240 + 241 + /** 242 + * @inline decorator for forcing a model to be inlined instead of creating a separate def 243 + */ 244 + export function $inline(context: DecoratorContext, target: Type) { 245 + context.program.stateSet(inlineKey).add(target); 246 + } 247 + 248 + export function isInline(program: Program, target: Type): boolean { 249 + return program.stateSet(inlineKey).has(target); 250 + }
+5
packages/emitter/src/emitter.ts
··· 45 45 getErrors, 46 46 isErrorModel, 47 47 getEncoding, 48 + isInline, 48 49 } from "./decorators.js"; 49 50 50 51 export interface EmitterOptions { ··· 284 285 } 285 286 286 287 if (isErrorModel(this.program, model)) return; 288 + if (isInline(this.program, model)) return; 287 289 288 290 const defName = model.name.charAt(0).toLowerCase() + model.name.slice(1); 289 291 const description = getDoc(this.program, model); ··· 1046 1048 private getModelReference(model: Model): string | null { 1047 1049 if (!model.name || !model.namespace || model.namespace.name === "TypeSpec") 1048 1050 return null; 1051 + 1052 + // If model is marked as @inline, don't create a reference - inline it instead 1053 + if (isInline(this.program, model)) return null; 1049 1054 1050 1055 const namespaceName = getNamespaceFullName(model.namespace); 1051 1056 if (!namespaceName) return null;
+2
packages/emitter/src/tsp-index.ts
··· 13 13 $subscription, 14 14 $encoding, 15 15 $errors, 16 + $inline, 16 17 } from "./decorators.js"; 17 18 18 19 /** @internal */ ··· 31 32 subscription: $subscription, 32 33 encoding: $encoding, 33 34 errors: $errors, 35 + inline: $inline, 34 36 }, 35 37 "Tlex.Private": { 36 38 blob: $blob,
+7
packages/emitter/test/scenarios/atproto/input/com/atproto/server/activateAccount.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.activateAccount { 4 + @doc("Activates a currently deactivated account. Used to finalize account migration after the account's repo is imported and identity is setup.") 5 + @procedure 6 + op main(): void; 7 + }
+17
packages/emitter/test/scenarios/atproto/input/com/atproto/server/checkAccountStatus.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.checkAccountStatus { 4 + @doc("Returns the status of an account, especially as pertaining to import or recovery. Can be called many times over the course of an account migration. Requires auth and can only be called pertaining to oneself.") 5 + @query 6 + op main(): { 7 + @required activated: boolean; 8 + @required validDid: boolean; 9 + @required repoCommit: cid; 10 + @required repoRev: string; 11 + @required repoBlocks: integer; 12 + @required indexedRecords: integer; 13 + @required privateStateValues: integer; 14 + @required expectedBlobs: integer; 15 + @required importedBlobs: integer; 16 + }; 17 + }
+16
packages/emitter/test/scenarios/atproto/input/com/atproto/server/confirmEmail.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.confirmEmail { 4 + model AccountNotFound {} 5 + model ExpiredToken {} 6 + model InvalidToken {} 7 + model InvalidEmail {} 8 + 9 + @doc("Confirm an email using a token from com.atproto.server.requestEmailConfirmation.") 10 + @procedure 11 + @errors(AccountNotFound, ExpiredToken, InvalidToken, InvalidEmail) 12 + op main(input: { 13 + @required email: string; 14 + @required token: string; 15 + }): void; 16 + }
+53
packages/emitter/test/scenarios/atproto/input/com/atproto/server/createAccount.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.createAccount { 4 + model InvalidHandle {} 5 + model InvalidPassword {} 6 + model InvalidInviteCode {} 7 + model HandleNotAvailable {} 8 + model UnsupportedDomain {} 9 + model UnresolvableDid {} 10 + model IncompatibleDidDoc {} 11 + 12 + @doc("Account login session returned on successful account creation.") 13 + @inline 14 + model Output { 15 + @required accessJwt: string; 16 + @required refreshJwt: string; 17 + @required handle: handle; 18 + 19 + @doc("The DID of the new account.") 20 + @required 21 + did: did; 22 + 23 + @doc("Complete DID document.") 24 + didDoc?: unknown; 25 + } 26 + 27 + @doc("Create an account. Implemented by PDS.") 28 + @procedure 29 + @errors(InvalidHandle, InvalidPassword, InvalidInviteCode, HandleNotAvailable, UnsupportedDomain, UnresolvableDid, IncompatibleDidDoc) 30 + op main(input: { 31 + email?: string; 32 + 33 + @doc("Requested handle for the account.") 34 + @required 35 + handle: handle; 36 + 37 + @doc("Pre-existing atproto DID, being imported to a new account.") 38 + did?: did; 39 + 40 + inviteCode?: string; 41 + verificationCode?: string; 42 + verificationPhone?: string; 43 + 44 + @doc("Initial account password. May need to meet instance-specific password strength requirements.") 45 + password?: string; 46 + 47 + @doc("DID PLC rotation key (aka, recovery key) to be included in PLC creation operation.") 48 + recoveryKey?: string; 49 + 50 + @doc("A signed DID PLC operation to be submitted as part of importing an existing account to this instance. NOTE: this optional field may be updated when full account migration is implemented.") 51 + plcOp?: unknown; 52 + }): Output; 53 + }
+24
packages/emitter/test/scenarios/atproto/input/com/atproto/server/createAppPassword.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.createAppPassword { 4 + model AccountTakedown {} 5 + 6 + model AppPassword { 7 + @required name: string; 8 + @required password: string; 9 + @required createdAt: datetime; 10 + privileged?: boolean; 11 + } 12 + 13 + @doc("Create an App Password.") 14 + @procedure 15 + @errors(AccountTakedown) 16 + op main(input: { 17 + @doc("A short name for the App Password, to help distinguish them.") 18 + @required 19 + name: string; 20 + 21 + @doc("If an app password has 'privileged' access to possibly sensitive account state. Meant for use with trusted clients.") 22 + privileged?: boolean; 23 + }): AppPassword; 24 + }
+12
packages/emitter/test/scenarios/atproto/input/com/atproto/server/createInviteCode.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.createInviteCode { 4 + @doc("Create an invite code.") 5 + @procedure 6 + op main(input: { 7 + @required useCount: integer; 8 + forAccount?: did; 9 + }): { 10 + @required code: string; 11 + }; 12 + }
+18
packages/emitter/test/scenarios/atproto/input/com/atproto/server/createInviteCodes.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.createInviteCodes { 4 + model AccountCodes { 5 + @required account: string; 6 + @required codes: string[]; 7 + } 8 + 9 + @doc("Create invite codes.") 10 + @procedure 11 + op main(input: { 12 + @required codeCount: integer = 1; 13 + @required useCount: integer; 14 + forAccounts?: did[]; 15 + }): { 16 + @required codes: AccountCodes[]; 17 + }; 18 + }
+10
packages/emitter/test/scenarios/atproto/input/com/atproto/server/deactivateAccount.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.deactivateAccount { 4 + @doc("Deactivates a currently active account. Stops serving of repo, and future writes to repo until reactivated. Used to finalize account migration with the old host after the account has been activated on the new host.") 5 + @procedure 6 + op main(input: { 7 + @doc("A recommendation to server as to how long they should hold onto the deactivated account before deleting.") 8 + deleteAfter?: datetime; 9 + }): void; 10 + }
+15
packages/emitter/test/scenarios/atproto/input/com/atproto/server/deleteAccount.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.deleteAccount { 4 + model ExpiredToken {} 5 + model InvalidToken {} 6 + 7 + @doc("Delete an actor's account with a token and password. Can only be called after requesting a deletion token. Requires auth.") 8 + @procedure 9 + @errors(ExpiredToken, InvalidToken) 10 + op main(input: { 11 + @required did: did; 12 + @required password: string; 13 + @required token: string; 14 + }): void; 15 + }
+18
packages/emitter/test/scenarios/atproto/input/com/atproto/server/getAccountInviteCodes.tsp
··· 1 + import "@tlex/emitter"; 2 + import "./defs.tsp"; 3 + 4 + namespace com.atproto.server.getAccountInviteCodes { 5 + model DuplicateCreate {} 6 + 7 + @doc("Get all invite codes for the current account. Requires auth.") 8 + @query 9 + @errors(DuplicateCreate) 10 + op main( 11 + includeUsed?: boolean = true, 12 + 13 + @doc("Controls whether any new 'earned' but not 'created' invites should be created.") 14 + createAvailable?: boolean = true 15 + ): { 16 + @required codes: com.atproto.server.defs.InviteCode[]; 17 + }; 18 + }
+23
packages/emitter/test/scenarios/atproto/input/com/atproto/server/getServiceAuth.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.getServiceAuth { 4 + @doc("Indicates that the requested expiration date is not a valid. May be in the past or may be reliant on the requested scopes.") 5 + model BadExpiration {} 6 + 7 + @doc("Get a signed token on behalf of the requesting DID for the requested service.") 8 + @query 9 + @errors(BadExpiration) 10 + op main( 11 + @doc("The DID of the service that the token will be used to authenticate with") 12 + @required 13 + aud: did, 14 + 15 + @doc("The time in Unix Epoch seconds that the JWT expires. Defaults to 60 seconds in the future. The service may enforce certain time bounds on tokens depending on the requested scope.") 16 + exp?: integer, 17 + 18 + @doc("Lexicon (XRPC) method to bind the requested token to") 19 + lxm?: nsid 20 + ): { 21 + @required token: string; 22 + }; 23 + }
+18
packages/emitter/test/scenarios/atproto/input/com/atproto/server/listAppPasswords.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.listAppPasswords { 4 + model AccountTakedown {} 5 + 6 + model AppPassword { 7 + @required name: string; 8 + @required createdAt: datetime; 9 + privileged?: boolean; 10 + } 11 + 12 + @doc("List all App Passwords.") 13 + @query 14 + @errors(AccountTakedown) 15 + op main(): { 16 + @required passwords: AppPassword[]; 17 + }; 18 + }
+20
packages/emitter/test/scenarios/atproto/input/com/atproto/server/refreshSession.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.refreshSession { 4 + model AccountTakedown {} 5 + 6 + @doc("Refresh an authentication session. Requires auth using the 'refreshJwt' (not the 'accessJwt').") 7 + @procedure 8 + @errors(AccountTakedown) 9 + op main(): { 10 + @required accessJwt: string; 11 + @required refreshJwt: string; 12 + @required handle: handle; 13 + @required did: did; 14 + didDoc?: unknown; 15 + active?: boolean; 16 + 17 + @doc("Hosting status of the account. If not specified, then assume 'active'.") 18 + status?: "takendown" | "suspended" | "deactivated" | string; 19 + }; 20 + }
+7
packages/emitter/test/scenarios/atproto/input/com/atproto/server/requestAccountDelete.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.requestAccountDelete { 4 + @doc("Initiate a user account deletion via email.") 5 + @procedure 6 + op main(): void; 7 + }
+7
packages/emitter/test/scenarios/atproto/input/com/atproto/server/requestEmailConfirmation.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.requestEmailConfirmation { 4 + @doc("Request an email with a code to confirm ownership of email.") 5 + @procedure 6 + op main(): void; 7 + }
+9
packages/emitter/test/scenarios/atproto/input/com/atproto/server/requestEmailUpdate.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.requestEmailUpdate { 4 + @doc("Request a token in order to update email.") 5 + @procedure 6 + op main(): { 7 + @required tokenRequired: boolean; 8 + }; 9 + }
+9
packages/emitter/test/scenarios/atproto/input/com/atproto/server/requestPasswordReset.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.requestPasswordReset { 4 + @doc("Initiate a user account password reset via email.") 5 + @procedure 6 + op main(input: { 7 + @required email: string; 8 + }): void; 9 + }
+14
packages/emitter/test/scenarios/atproto/input/com/atproto/server/reserveSigningKey.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.reserveSigningKey { 4 + @doc("Reserve a repo signing key, for use with account creation. Necessary so that a DID PLC update operation can be constructed during an account migraiton. Public and does not require auth; implemented by PDS. NOTE: this endpoint may change when full account migration is implemented.") 5 + @procedure 6 + op main(input: { 7 + @doc("The DID to reserve a key for.") 8 + did?: did; 9 + }): { 10 + @doc("The public key for the reserved signing key, in did:key serialization.") 11 + @required 12 + signingKey: string; 13 + }; 14 + }
+14
packages/emitter/test/scenarios/atproto/input/com/atproto/server/resetPassword.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.resetPassword { 4 + model ExpiredToken {} 5 + model InvalidToken {} 6 + 7 + @doc("Reset a user account password using a token.") 8 + @procedure 9 + @errors(ExpiredToken, InvalidToken) 10 + op main(input: { 11 + @required token: string; 12 + @required password: string; 13 + }): void; 14 + }
+9
packages/emitter/test/scenarios/atproto/input/com/atproto/server/revokeAppPassword.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.revokeAppPassword { 4 + @doc("Revoke an App Password by name.") 5 + @procedure 6 + op main(input: { 7 + @required name: string; 8 + }): void; 9 + }
+18
packages/emitter/test/scenarios/atproto/input/com/atproto/server/updateEmail.tsp
··· 1 + import "@tlex/emitter"; 2 + 3 + namespace com.atproto.server.updateEmail { 4 + model ExpiredToken {} 5 + model InvalidToken {} 6 + model TokenRequired {} 7 + 8 + @doc("Update an account's email.") 9 + @procedure 10 + @errors(ExpiredToken, InvalidToken, TokenRequired) 11 + op main(input: { 12 + @required email: string; 13 + emailAuthFactor?: boolean; 14 + 15 + @doc("Requires a token from com.atproto.sever.requestEmailUpdate if the account's email has been confirmed.") 16 + token?: string; 17 + }): void; 18 + }