@atproto/lex-builder 0.0.13 → 0.0.14
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 +10 -0
- package/dist/filter.d.ts +55 -0
- package/dist/filter.d.ts.map +1 -1
- package/dist/filter.js +22 -0
- package/dist/filter.js.map +1 -1
- package/dist/formatter.d.ts +42 -1
- package/dist/formatter.d.ts.map +1 -1
- package/dist/formatter.js +25 -0
- package/dist/formatter.js.map +1 -1
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -1
- package/dist/lex-builder.d.ts +69 -0
- package/dist/lex-builder.d.ts.map +1 -1
- package/dist/lex-builder.js +20 -0
- package/dist/lex-builder.js.map +1 -1
- package/dist/lex-def-builder.d.ts +37 -1
- package/dist/lex-def-builder.d.ts.map +1 -1
- package/dist/lex-def-builder.js +11 -1
- package/dist/lex-def-builder.js.map +1 -1
- package/dist/lexicon-directory-indexer.d.ts +13 -0
- package/dist/lexicon-directory-indexer.d.ts.map +1 -1
- package/dist/lexicon-directory-indexer.js +8 -0
- package/dist/lexicon-directory-indexer.js.map +1 -1
- package/dist/ref-resolver.d.ts +71 -2
- package/dist/ref-resolver.d.ts.map +1 -1
- package/dist/ref-resolver.js +44 -2
- package/dist/ref-resolver.js.map +1 -1
- package/dist/ts-lang.d.ts +44 -0
- package/dist/ts-lang.d.ts.map +1 -1
- package/dist/ts-lang.js +48 -1
- package/dist/ts-lang.js.map +1 -1
- package/package.json +3 -3
- package/src/filter.ts +55 -0
- package/src/formatter.ts +42 -1
- package/src/index.ts +33 -0
- package/src/lex-builder.ts +69 -0
- package/src/lex-def-builder.ts +37 -1
- package/src/lexicon-directory-indexer.ts +13 -0
- package/src/ref-resolver.ts +71 -2
- package/src/ts-lang.ts +48 -1
package/dist/ref-resolver.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ref-resolver.js","sourceRoot":"","sources":["../src/ref-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from 'node:assert'\nimport { join } from 'node:path'\nimport { SourceFile } from 'ts-morph'\nimport { LexiconDocument, LexiconIndexer } from '@atproto/lex-document'\nimport {\n isGlobalIdentifier,\n isJsKeyword,\n isSafeLocalIdentifier,\n isValidJsIdentifier,\n} from './ts-lang.js'\nimport {\n asRelativePath,\n memoize,\n startsWithLower,\n toCamelCase,\n toPascalCase,\n ucFirst,\n} from './util.js'\n\nexport type RefResolverOptions = {\n importExt?: string\n}\n\nexport type ResolvedRef = {\n varName: string\n typeName: string\n}\n\n/**\n * Utility class to resolve lexicon references to TypeScript identifiers,\n * generating \"import\" statements as needed.\n */\nexport class RefResolver {\n constructor(\n private doc: LexiconDocument,\n private file: SourceFile,\n private indexer: LexiconIndexer,\n private options: RefResolverOptions,\n ) {}\n\n public readonly resolve = memoize(\n async (ref: string): Promise<ResolvedRef> => {\n const [nsid, hash = 'main'] = ref.split('#')\n\n if (nsid === '' || nsid === this.doc.id) {\n return this.resolveLocal(hash)\n } else {\n // @NOTE: Normalize (#main fragment) to ensure proper memoization\n const fullRef = `${nsid}#${hash}`\n return this.resolveExternal(fullRef)\n }\n },\n )\n\n #defCounters = new Map<string, number>()\n private nextSafeDefinitionIdentifier(name: string) {\n // use camelCase version of the hash as base name\n const nameSafe =\n startsWithLower(name) && isValidJsIdentifier(name)\n ? name\n : toCamelCase(name).replace(/^[0-9]+/g, '') || 'def'\n\n const count = this.#defCounters.get(nameSafe) ?? 0\n this.#defCounters.set(nameSafe, count + 1)\n\n // @NOTE We don't need to check against local declarations in the file here\n // since we are using a naming system that should guarantee no other\n // identifier has a <nameSafe>$<number> format (\"$\" cannot appear in\n // hashes so only *we* are generating such identifiers).\n\n const identifier = `${nameSafe}$${count}`\n\n assert(\n isValidJsIdentifier(identifier),\n `Unable to generate safe identifier for: \"${name}\"`,\n )\n\n return identifier\n }\n\n /**\n * @note The returned `typeName` and `varName` are *both* guaranteed to be\n * valid TypeScript identifiers.\n */\n public readonly resolveLocal = memoize(\n async (hash: string): Promise<ResolvedRef> => {\n const hashes = Object.keys(this.doc.defs)\n\n if (!hashes.includes(hash)) {\n throw new Error(`Definition ${hash} not found in ${this.doc.id}`)\n }\n\n // Because we are using predictable \"public\" identifiers for type names,\n // we need to ensure there are no conflicts between different definitions\n // in the same lexicon document.\n //\n // @NOTE It should be possible to implement a way to generate\n // non-conflicting type names for all public (type) identifiers in a\n // project. However, this would add a lot of complexity to the code\n // generation process, and the likelihood of such conflicts happening in\n // practice is very low, so we opt for a simpler approach of just throwing\n // an error if a conflict is detected.\n const pub = getPublicIdentifiers(hash)\n for (const otherHash of hashes) {\n if (otherHash === hash) continue\n const otherPub = getPublicIdentifiers(otherHash)\n if (otherPub.typeName === pub.typeName) {\n throw new Error(\n `Conflicting type names for definitions #${hash} and #${otherHash} in ${this.doc.id}`,\n )\n }\n }\n\n // Try to keep and identifier that resembles the original hash as identifier\n const safeIdentifier = asSafeDefinitionIdentifier(hash)\n\n // If the safe identifier is not conflicting with other definition names,\n // or reserved words, we can use it as-is. Otherwise, we need to generate\n // a unique safe identifier.\n const varName = safeIdentifier\n ? !hashes.some((otherHash) => {\n if (otherHash === hash) return false\n const otherIdentifier = asSafeDefinitionIdentifier(otherHash)\n return otherIdentifier === safeIdentifier\n })\n ? // Safe identifier can be used as-is as it does not conflict with\n // other definition names\n safeIdentifier\n : // In order to keep identifiers stable, we use the safe identifier\n // as base, and append a counter to avoid conflicts\n this.nextSafeDefinitionIdentifier(safeIdentifier)\n : // hash only contained unsafe characters, generate a safe one\n this.nextSafeDefinitionIdentifier(hash)\n\n const typeName = ucFirst(varName)\n assert(isSafeLocalIdentifier(typeName), 'Expected safe type identifier')\n assert(varName !== typeName, 'Variable and type name should be different')\n\n return { varName, typeName }\n },\n )\n\n /**\n * @note Since this is a memoized function, and is used to generate the name\n * of local variables, we should avoid returning different results for\n * similar, but non strictly equal, inputs (eg. normalized / non-normalized).\n * @see {@link resolve}\n */\n private readonly resolveExternal = memoize(\n async (fullRef: string): Promise<ResolvedRef> => {\n const [nsid, hash] = fullRef.split('#')\n const moduleSpecifier = `${asRelativePath(\n this.file.getDirectoryPath(),\n join('/', ...nsid.split('.')),\n )}.defs${this.options.importExt ?? '.js'}`\n\n // Lets first make sure the referenced lexicon exists\n const srcDoc = await this.indexer.get(nsid)\n const srcDef = Object.hasOwn(srcDoc.defs, hash) ? srcDoc.defs[hash] : null\n if (!srcDef) {\n throw new Error(\n `Missing def \"${hash}\" in \"${nsid}\" (referenced from ${this.doc.id})`,\n )\n }\n\n const publicIds = getPublicIdentifiers(hash)\n\n if (!isValidJsIdentifier(publicIds.typeName)) {\n // If <typeName> is not a valid identifier, we cannot access the type\n // using dot notation (<nsIdentifier>.<typeName>). Note that, unlike js\n // variables, types cannot be accessed using string indexing (like:\n // <nsIdentifier>['<typeName>']) because it generates TypeScript errors:\n //\n // > \"Cannot use namespace '<nsIdentifier>' as a type.\"\n\n // Instead the generated code should look like:\n // import { \"<unsafeTypeName>\" as <safeIdentifier> } from './<moduleSpecifier>'\n\n // Because it requires more complex management of local variables names,\n // and we don't expect this to actually happen with properly designed\n // lexicons documents, we do not support this for now.\n\n throw new Error(\n 'Import of definitions with unsafe type names is not supported',\n )\n }\n\n // import * as <nsIdentifier> from './<moduleSpecifier>'\n const nsIdentifier = this.getNsIdentifier(nsid, moduleSpecifier)\n\n return {\n varName: isValidJsIdentifier(publicIds.varName)\n ? `${nsIdentifier}.${publicIds.varName}`\n : `${nsIdentifier}[${JSON.stringify(publicIds.varName)}]`,\n typeName: `${nsIdentifier}.${publicIds.typeName}`,\n }\n },\n )\n\n private getNsIdentifier(nsid: string, moduleSpecifier: string) {\n const namespaceImportDeclaration =\n this.file.getImportDeclaration(\n (imp) =>\n !imp.isTypeOnly() &&\n imp.getModuleSpecifierValue() === moduleSpecifier &&\n imp.getNamespaceImport() != null,\n ) ||\n this.file.addImportDeclaration({\n moduleSpecifier,\n namespaceImport: this.computeSafeNamespaceIdentifierFor(nsid),\n })\n\n return namespaceImportDeclaration.getNamespaceImport()!.getText()\n }\n\n #nsIdentifiersCounters = new Map<string, number>()\n private computeSafeNamespaceIdentifierFor(nsid: string) {\n const baseName = nsidToIdentifier(nsid) || 'NS'\n\n let name = baseName\n while (this.isConflictingIdentifier(name)) {\n const count = this.#nsIdentifiersCounters.get(baseName) ?? 0\n this.#nsIdentifiersCounters.set(baseName, count + 1)\n name = `${baseName}$$${count}`\n }\n\n return name\n }\n\n private isConflictingIdentifier(name: string) {\n return (\n this.conflictsWithKeywords(name) ||\n this.conflictsWithUtils(name) ||\n this.conflictsWithLocalDefs(name) ||\n this.conflictsWithLocalDeclarations(name) ||\n this.conflictsWithImports(name)\n )\n }\n\n private conflictsWithKeywords(name: string) {\n return isJsKeyword(name) || isGlobalIdentifier(name)\n }\n\n private conflictsWithUtils(name: string) {\n // Do not allow \"Main\" as imported ns identifier since it has a special\n // meaning in the context of lexicon definitions.\n if (name === 'Main') return true\n\n // When \"useRecordExport\" returns true, an export named \"Record\" will be\n // used in addition to the hash named export. So we need to make sure both\n // names are not conflicting with local variables.\n if (name === 'Record') return true\n\n // Utility functions generated for lexicon schemas are prefixed with \"$\"\n return name.startsWith('$')\n }\n\n private conflictsWithLocalDefs(name: string) {\n return Object.keys(this.doc.defs).some((hash) => {\n const identifier = toCamelCase(hash)\n\n // A safe identifier will be generated, no risk of conflict.\n if (!identifier) return false\n\n // The imported name conflicts with a local definition name\n if (identifier === name || `_${identifier}` === name) return true\n\n // The imported name conflicts with the type name of a local definition\n const typeName = ucFirst(identifier)\n if (typeName === name || `_${typeName}` === name) return true\n\n return false\n })\n }\n\n private conflictsWithLocalDeclarations(name: string) {\n return (\n this.file.getVariableDeclarations().some((v) => v.getName() === name) ||\n this.file\n .getVariableStatements()\n .some((vs) => vs.getDeclarations().some((d) => d.getName() === name)) ||\n this.file.getTypeAliases().some((t) => t.getName() === name) ||\n this.file.getInterfaces().some((i) => i.getName() === name) ||\n this.file.getClasses().some((c) => c.getName() === name) ||\n this.file.getFunctions().some((f) => f.getName() === name) ||\n this.file.getEnums().some((e) => e.getName() === name)\n )\n }\n\n private conflictsWithImports(name: string) {\n return this.file.getImportDeclarations().some(\n (imp) =>\n // import name from '...'\n imp.getDefaultImport()?.getText() === name ||\n // import * as name from '...'\n imp.getNamespaceImport()?.getText() === name ||\n imp.getNamedImports().some(\n (named) =>\n // import { name } from '...'\n // import { foo as name } from '...'\n (named.getAliasNode()?.getText() ?? named.getName()) === name,\n ),\n )\n }\n}\n\n/**\n * @see {@link https://atproto.com/specs/nsid NSID syntax spec}\n */\nfunction nsidToIdentifier(nsid: string) {\n const parts = nsid.split('.')\n\n // By default, try to keep only to the last two segments of the NSID as\n // contextual information. If those do not form a safe identifier (typically\n // because they start with a digit), try with more segments until we reach the\n // full NSID.\n for (let i = 2; i < parts.length; i++) {\n const identifier = toPascalCase(parts.slice(-i).join('.'))\n if (isSafeLocalIdentifier(identifier)) return identifier\n }\n\n return undefined\n}\n\n/**\n * Generates predictable public identifiers for a given definition hash.\n *\n * @note The returned `typeName` is guaranteed to be a valid TypeScript\n * identifier. `varName` may not be a valid identifier (eg. if the hash contains\n * unsafe characters), and may need to be accessed using string indexing.\n */\nexport function getPublicIdentifiers(hash: string): ResolvedRef {\n const varName = hash\n\n // @NOTE we try to circumvent the issue of unsafe type names described in\n // `RefResolver.resolveExternal` by ensuring that type names are always safe\n // identifiers, even if it means changing them from the original hash.\n let typeName = toPascalCase(hash)\n\n if (varName === typeName || !isValidJsIdentifier(typeName)) {\n typeName = `TypeOf${typeName}`\n }\n\n assert(\n isValidJsIdentifier(typeName),\n `Unable to generate a predictable safe identifier for \"${hash}\"`,\n )\n\n return { varName, typeName }\n}\n\nfunction asSafeDefinitionIdentifier(name: string) {\n if (\n startsWithLower(name) &&\n isSafeLocalIdentifier(name) &&\n isSafeLocalIdentifier(ucFirst(name))\n ) {\n return name\n }\n const camel = toCamelCase(name)\n if (isSafeLocalIdentifier(camel) && isSafeLocalIdentifier(ucFirst(camel))) {\n return camel\n }\n return undefined\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ref-resolver.js","sourceRoot":"","sources":["../src/ref-resolver.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from 'node:assert'\nimport { join } from 'node:path'\nimport { SourceFile } from 'ts-morph'\nimport { LexiconDocument, LexiconIndexer } from '@atproto/lex-document'\nimport {\n isGlobalIdentifier,\n isJsKeyword,\n isSafeLocalIdentifier,\n isValidJsIdentifier,\n} from './ts-lang.js'\nimport {\n asRelativePath,\n memoize,\n startsWithLower,\n toCamelCase,\n toPascalCase,\n ucFirst,\n} from './util.js'\n\n/**\n * Configuration options for the {@link RefResolver} class.\n */\nexport type RefResolverOptions = {\n /**\n * The file extension to use for import specifiers when resolving\n * external references.\n *\n * @default '.js'\n */\n importExt?: string\n}\n\n/**\n * Represents a resolved lexicon reference as TypeScript identifiers.\n *\n * Contains the variable name (for runtime schema) and type name (for\n * TypeScript type) that can be used to reference a lexicon definition.\n */\nexport type ResolvedRef = {\n /**\n * The variable name for the runtime schema.\n *\n * For local definitions, this is a simple identifier.\n * For external definitions, this may be a qualified name like `ns.varName`\n * or bracket notation like `ns[\"varName\"]` for unsafe identifiers.\n */\n varName: string\n /**\n * The type name for the TypeScript type alias.\n *\n * Always a valid TypeScript identifier, either simple or qualified.\n */\n typeName: string\n}\n\n/**\n * Resolves lexicon references to TypeScript identifiers.\n *\n * This class handles the resolution of `ref` types in lexicon documents,\n * converting lexicon reference strings (like `com.example.foo#bar`) into\n * valid TypeScript identifiers. It automatically manages:\n *\n * - Local references within the same document\n * - External references to other lexicon documents\n * - Import statement generation for external references\n * - Conflict avoidance with keywords, globals, and existing declarations\n *\n * Results are memoized to ensure consistent identifiers for the same\n * reference throughout a file.\n *\n * @example\n * ```ts\n * const resolver = new RefResolver(doc, sourceFile, indexer, options)\n *\n * // Resolve a local reference\n * const local = await resolver.resolve('#myDef')\n *\n * // Resolve an external reference\n * const external = await resolver.resolve('com.example.other#def')\n * ```\n */\nexport class RefResolver {\n constructor(\n private doc: LexiconDocument,\n private file: SourceFile,\n private indexer: LexiconIndexer,\n private options: RefResolverOptions,\n ) {}\n\n public readonly resolve = memoize(\n async (ref: string): Promise<ResolvedRef> => {\n const [nsid, hash = 'main'] = ref.split('#')\n\n if (nsid === '' || nsid === this.doc.id) {\n return this.resolveLocal(hash)\n } else {\n // @NOTE: Normalize (#main fragment) to ensure proper memoization\n const fullRef = `${nsid}#${hash}`\n return this.resolveExternal(fullRef)\n }\n },\n )\n\n #defCounters = new Map<string, number>()\n private nextSafeDefinitionIdentifier(name: string) {\n // use camelCase version of the hash as base name\n const nameSafe =\n startsWithLower(name) && isValidJsIdentifier(name)\n ? name\n : toCamelCase(name).replace(/^[0-9]+/g, '') || 'def'\n\n const count = this.#defCounters.get(nameSafe) ?? 0\n this.#defCounters.set(nameSafe, count + 1)\n\n // @NOTE We don't need to check against local declarations in the file here\n // since we are using a naming system that should guarantee no other\n // identifier has a <nameSafe>$<number> format (\"$\" cannot appear in\n // hashes so only *we* are generating such identifiers).\n\n const identifier = `${nameSafe}$${count}`\n\n assert(\n isValidJsIdentifier(identifier),\n `Unable to generate safe identifier for: \"${name}\"`,\n )\n\n return identifier\n }\n\n /**\n * Resolves a local definition hash to TypeScript identifiers.\n *\n * This method generates safe, non-conflicting identifiers for definitions\n * within the current document. It handles edge cases like:\n * - Hash names that are JavaScript keywords\n * - Hash names that conflict with global identifiers\n * - Multiple hashes that would produce the same identifier\n *\n * @param hash - The definition hash (e.g., 'main', 'record', 'myType')\n * @returns A promise resolving to the TypeScript identifiers\n * @throws Error if the hash does not exist in the document\n * @throws Error if conflicting type names are detected\n *\n * @note The returned `typeName` and `varName` are *both* guaranteed to be\n * valid TypeScript identifiers.\n */\n public readonly resolveLocal = memoize(\n async (hash: string): Promise<ResolvedRef> => {\n const hashes = Object.keys(this.doc.defs)\n\n if (!hashes.includes(hash)) {\n throw new Error(`Definition ${hash} not found in ${this.doc.id}`)\n }\n\n // Because we are using predictable \"public\" identifiers for type names,\n // we need to ensure there are no conflicts between different definitions\n // in the same lexicon document.\n //\n // @NOTE It should be possible to implement a way to generate\n // non-conflicting type names for all public (type) identifiers in a\n // project. However, this would add a lot of complexity to the code\n // generation process, and the likelihood of such conflicts happening in\n // practice is very low, so we opt for a simpler approach of just throwing\n // an error if a conflict is detected.\n const pub = getPublicIdentifiers(hash)\n for (const otherHash of hashes) {\n if (otherHash === hash) continue\n const otherPub = getPublicIdentifiers(otherHash)\n if (otherPub.typeName === pub.typeName) {\n throw new Error(\n `Conflicting type names for definitions #${hash} and #${otherHash} in ${this.doc.id}`,\n )\n }\n }\n\n // Try to keep and identifier that resembles the original hash as identifier\n const safeIdentifier = asSafeDefinitionIdentifier(hash)\n\n // If the safe identifier is not conflicting with other definition names,\n // or reserved words, we can use it as-is. Otherwise, we need to generate\n // a unique safe identifier.\n const varName = safeIdentifier\n ? !hashes.some((otherHash) => {\n if (otherHash === hash) return false\n const otherIdentifier = asSafeDefinitionIdentifier(otherHash)\n return otherIdentifier === safeIdentifier\n })\n ? // Safe identifier can be used as-is as it does not conflict with\n // other definition names\n safeIdentifier\n : // In order to keep identifiers stable, we use the safe identifier\n // as base, and append a counter to avoid conflicts\n this.nextSafeDefinitionIdentifier(safeIdentifier)\n : // hash only contained unsafe characters, generate a safe one\n this.nextSafeDefinitionIdentifier(hash)\n\n const typeName = ucFirst(varName)\n assert(isSafeLocalIdentifier(typeName), 'Expected safe type identifier')\n assert(varName !== typeName, 'Variable and type name should be different')\n\n return { varName, typeName }\n },\n )\n\n /**\n * @note Since this is a memoized function, and is used to generate the name\n * of local variables, we should avoid returning different results for\n * similar, but non strictly equal, inputs (eg. normalized / non-normalized).\n * @see {@link resolve}\n */\n private readonly resolveExternal = memoize(\n async (fullRef: string): Promise<ResolvedRef> => {\n const [nsid, hash] = fullRef.split('#')\n const moduleSpecifier = `${asRelativePath(\n this.file.getDirectoryPath(),\n join('/', ...nsid.split('.')),\n )}.defs${this.options.importExt ?? '.js'}`\n\n // Lets first make sure the referenced lexicon exists\n const srcDoc = await this.indexer.get(nsid)\n const srcDef = Object.hasOwn(srcDoc.defs, hash) ? srcDoc.defs[hash] : null\n if (!srcDef) {\n throw new Error(\n `Missing def \"${hash}\" in \"${nsid}\" (referenced from ${this.doc.id})`,\n )\n }\n\n const publicIds = getPublicIdentifiers(hash)\n\n if (!isValidJsIdentifier(publicIds.typeName)) {\n // If <typeName> is not a valid identifier, we cannot access the type\n // using dot notation (<nsIdentifier>.<typeName>). Note that, unlike js\n // variables, types cannot be accessed using string indexing (like:\n // <nsIdentifier>['<typeName>']) because it generates TypeScript errors:\n //\n // > \"Cannot use namespace '<nsIdentifier>' as a type.\"\n\n // Instead the generated code should look like:\n // import { \"<unsafeTypeName>\" as <safeIdentifier> } from './<moduleSpecifier>'\n\n // Because it requires more complex management of local variables names,\n // and we don't expect this to actually happen with properly designed\n // lexicons documents, we do not support this for now.\n\n throw new Error(\n 'Import of definitions with unsafe type names is not supported',\n )\n }\n\n // import * as <nsIdentifier> from './<moduleSpecifier>'\n const nsIdentifier = this.getNsIdentifier(nsid, moduleSpecifier)\n\n return {\n varName: isValidJsIdentifier(publicIds.varName)\n ? `${nsIdentifier}.${publicIds.varName}`\n : `${nsIdentifier}[${JSON.stringify(publicIds.varName)}]`,\n typeName: `${nsIdentifier}.${publicIds.typeName}`,\n }\n },\n )\n\n private getNsIdentifier(nsid: string, moduleSpecifier: string) {\n const namespaceImportDeclaration =\n this.file.getImportDeclaration(\n (imp) =>\n !imp.isTypeOnly() &&\n imp.getModuleSpecifierValue() === moduleSpecifier &&\n imp.getNamespaceImport() != null,\n ) ||\n this.file.addImportDeclaration({\n moduleSpecifier,\n namespaceImport: this.computeSafeNamespaceIdentifierFor(nsid),\n })\n\n return namespaceImportDeclaration.getNamespaceImport()!.getText()\n }\n\n #nsIdentifiersCounters = new Map<string, number>()\n private computeSafeNamespaceIdentifierFor(nsid: string) {\n const baseName = nsidToIdentifier(nsid) || 'NS'\n\n let name = baseName\n while (this.isConflictingIdentifier(name)) {\n const count = this.#nsIdentifiersCounters.get(baseName) ?? 0\n this.#nsIdentifiersCounters.set(baseName, count + 1)\n name = `${baseName}$$${count}`\n }\n\n return name\n }\n\n private isConflictingIdentifier(name: string) {\n return (\n this.conflictsWithKeywords(name) ||\n this.conflictsWithUtils(name) ||\n this.conflictsWithLocalDefs(name) ||\n this.conflictsWithLocalDeclarations(name) ||\n this.conflictsWithImports(name)\n )\n }\n\n private conflictsWithKeywords(name: string) {\n return isJsKeyword(name) || isGlobalIdentifier(name)\n }\n\n private conflictsWithUtils(name: string) {\n // Do not allow \"Main\" as imported ns identifier since it has a special\n // meaning in the context of lexicon definitions.\n if (name === 'Main') return true\n\n // When \"useRecordExport\" returns true, an export named \"Record\" will be\n // used in addition to the hash named export. So we need to make sure both\n // names are not conflicting with local variables.\n if (name === 'Record') return true\n\n // Utility functions generated for lexicon schemas are prefixed with \"$\"\n return name.startsWith('$')\n }\n\n private conflictsWithLocalDefs(name: string) {\n return Object.keys(this.doc.defs).some((hash) => {\n const identifier = toCamelCase(hash)\n\n // A safe identifier will be generated, no risk of conflict.\n if (!identifier) return false\n\n // The imported name conflicts with a local definition name\n if (identifier === name || `_${identifier}` === name) return true\n\n // The imported name conflicts with the type name of a local definition\n const typeName = ucFirst(identifier)\n if (typeName === name || `_${typeName}` === name) return true\n\n return false\n })\n }\n\n private conflictsWithLocalDeclarations(name: string) {\n return (\n this.file.getVariableDeclarations().some((v) => v.getName() === name) ||\n this.file\n .getVariableStatements()\n .some((vs) => vs.getDeclarations().some((d) => d.getName() === name)) ||\n this.file.getTypeAliases().some((t) => t.getName() === name) ||\n this.file.getInterfaces().some((i) => i.getName() === name) ||\n this.file.getClasses().some((c) => c.getName() === name) ||\n this.file.getFunctions().some((f) => f.getName() === name) ||\n this.file.getEnums().some((e) => e.getName() === name)\n )\n }\n\n private conflictsWithImports(name: string) {\n return this.file.getImportDeclarations().some(\n (imp) =>\n // import name from '...'\n imp.getDefaultImport()?.getText() === name ||\n // import * as name from '...'\n imp.getNamespaceImport()?.getText() === name ||\n imp.getNamedImports().some(\n (named) =>\n // import { name } from '...'\n // import { foo as name } from '...'\n (named.getAliasNode()?.getText() ?? named.getName()) === name,\n ),\n )\n }\n}\n\n/**\n * @see {@link https://atproto.com/specs/nsid NSID syntax spec}\n */\nfunction nsidToIdentifier(nsid: string) {\n const parts = nsid.split('.')\n\n // By default, try to keep only to the last two segments of the NSID as\n // contextual information. If those do not form a safe identifier (typically\n // because they start with a digit), try with more segments until we reach the\n // full NSID.\n for (let i = 2; i < parts.length; i++) {\n const identifier = toPascalCase(parts.slice(-i).join('.'))\n if (isSafeLocalIdentifier(identifier)) return identifier\n }\n\n return undefined\n}\n\n/**\n * Generates predictable public identifiers for a given definition hash.\n *\n * This function creates the \"public\" names that will be exported from\n * generated files. The variable name uses the original hash, while the\n * type name is converted to PascalCase.\n *\n * @param hash - The definition hash (e.g., 'main', 'myType')\n * @returns The public identifiers for the definition\n *\n * @note The returned `typeName` is guaranteed to be a valid TypeScript\n * identifier. `varName` may not be a valid identifier (eg. if the hash contains\n * unsafe characters), and may need to be accessed using string indexing.\n */\nexport function getPublicIdentifiers(hash: string): ResolvedRef {\n const varName = hash\n\n // @NOTE we try to circumvent the issue of unsafe type names described in\n // `RefResolver.resolveExternal` by ensuring that type names are always safe\n // identifiers, even if it means changing them from the original hash.\n let typeName = toPascalCase(hash)\n\n if (varName === typeName || !isValidJsIdentifier(typeName)) {\n typeName = `TypeOf${typeName}`\n }\n\n assert(\n isValidJsIdentifier(typeName),\n `Unable to generate a predictable safe identifier for \"${hash}\"`,\n )\n\n return { varName, typeName }\n}\n\nfunction asSafeDefinitionIdentifier(name: string) {\n if (\n startsWithLower(name) &&\n isSafeLocalIdentifier(name) &&\n isSafeLocalIdentifier(ucFirst(name))\n ) {\n return name\n }\n const camel = toCamelCase(name)\n if (isSafeLocalIdentifier(camel) && isSafeLocalIdentifier(ucFirst(camel))) {\n return camel\n }\n return undefined\n}\n"]}
|
package/dist/ts-lang.d.ts
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a word is a JavaScript reserved keyword.
|
|
3
|
+
*
|
|
4
|
+
* @param word - The identifier to check
|
|
5
|
+
* @returns `true` if the word is a reserved keyword
|
|
6
|
+
*/
|
|
1
7
|
export declare function isJsKeyword(word: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Checks if a word is a global identifier that should be avoided.
|
|
10
|
+
*
|
|
11
|
+
* This includes JavaScript globals, TypeScript built-in types, and
|
|
12
|
+
* identifiers commonly used in the generated code.
|
|
13
|
+
*
|
|
14
|
+
* @param word - The identifier to check
|
|
15
|
+
* @returns `true` if the word is a global identifier
|
|
16
|
+
*/
|
|
2
17
|
export declare function isGlobalIdentifier(word: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Checks if a name is safe to use as a local identifier.
|
|
20
|
+
*
|
|
21
|
+
* A safe local identifier is a valid JavaScript identifier that does not
|
|
22
|
+
* conflict with global identifiers.
|
|
23
|
+
*
|
|
24
|
+
* @param name - The identifier to check
|
|
25
|
+
* @returns `true` if the name is safe to use locally
|
|
26
|
+
*/
|
|
3
27
|
export declare function isSafeLocalIdentifier(name: string): boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Checks if a name is a valid JavaScript identifier.
|
|
30
|
+
*
|
|
31
|
+
* Valid identifiers start with a letter, underscore, or dollar sign,
|
|
32
|
+
* followed by any combination of letters, digits, underscores, or dollar
|
|
33
|
+
* signs. Reserved keywords are not valid identifiers.
|
|
34
|
+
*
|
|
35
|
+
* @param name - The string to check
|
|
36
|
+
* @returns `true` if the name is a valid identifier
|
|
37
|
+
*/
|
|
4
38
|
export declare function isValidJsIdentifier(name: string): boolean;
|
|
39
|
+
/**
|
|
40
|
+
* Converts a name to a valid namespace export identifier.
|
|
41
|
+
*
|
|
42
|
+
* If the name is a valid JavaScript identifier, it is returned as-is.
|
|
43
|
+
* Otherwise, it is returned as a quoted string for use in export statements
|
|
44
|
+
* like `export { foo as "unsafe-name" }`.
|
|
45
|
+
*
|
|
46
|
+
* @param name - The export name
|
|
47
|
+
* @returns The name as a valid export identifier
|
|
48
|
+
*/
|
|
5
49
|
export declare function asNamespaceExport(name: string): string;
|
|
6
50
|
//# sourceMappingURL=ts-lang.d.ts.map
|
package/dist/ts-lang.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ts-lang.d.ts","sourceRoot":"","sources":["../src/ts-lang.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ts-lang.d.ts","sourceRoot":"","sources":["../src/ts-lang.ts"],"names":[],"mappings":"AAkFA;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,WAEvC;AAkDD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,WAE9C;AAED;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,WAEjD;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,WAM/C;AAED;;;;;;;;;GASG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,UAE7C"}
|
package/dist/ts-lang.js
CHANGED
|
@@ -6,7 +6,10 @@ exports.isSafeLocalIdentifier = isSafeLocalIdentifier;
|
|
|
6
6
|
exports.isValidJsIdentifier = isValidJsIdentifier;
|
|
7
7
|
exports.asNamespaceExport = asNamespaceExport;
|
|
8
8
|
/**
|
|
9
|
-
* JavaScript keywords
|
|
9
|
+
* Set of JavaScript reserved keywords and future reserved words.
|
|
10
|
+
*
|
|
11
|
+
* These identifiers cannot be used as variable or type names in generated code.
|
|
12
|
+
*
|
|
10
13
|
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar}
|
|
11
14
|
*/
|
|
12
15
|
const JS_KEYWORDS = new Set([
|
|
@@ -83,6 +86,12 @@ const JS_KEYWORDS = new Set([
|
|
|
83
86
|
'with',
|
|
84
87
|
'yield',
|
|
85
88
|
]);
|
|
89
|
+
/**
|
|
90
|
+
* Checks if a word is a JavaScript reserved keyword.
|
|
91
|
+
*
|
|
92
|
+
* @param word - The identifier to check
|
|
93
|
+
* @returns `true` if the word is a reserved keyword
|
|
94
|
+
*/
|
|
86
95
|
function isJsKeyword(word) {
|
|
87
96
|
return JS_KEYWORDS.has(word);
|
|
88
97
|
}
|
|
@@ -133,17 +142,55 @@ const GLOBAL_IDENTIFIERS = new Set([
|
|
|
133
142
|
'Capitalize',
|
|
134
143
|
'Uncapitalize',
|
|
135
144
|
]);
|
|
145
|
+
/**
|
|
146
|
+
* Checks if a word is a global identifier that should be avoided.
|
|
147
|
+
*
|
|
148
|
+
* This includes JavaScript globals, TypeScript built-in types, and
|
|
149
|
+
* identifiers commonly used in the generated code.
|
|
150
|
+
*
|
|
151
|
+
* @param word - The identifier to check
|
|
152
|
+
* @returns `true` if the word is a global identifier
|
|
153
|
+
*/
|
|
136
154
|
function isGlobalIdentifier(word) {
|
|
137
155
|
return GLOBAL_IDENTIFIERS.has(word);
|
|
138
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Checks if a name is safe to use as a local identifier.
|
|
159
|
+
*
|
|
160
|
+
* A safe local identifier is a valid JavaScript identifier that does not
|
|
161
|
+
* conflict with global identifiers.
|
|
162
|
+
*
|
|
163
|
+
* @param name - The identifier to check
|
|
164
|
+
* @returns `true` if the name is safe to use locally
|
|
165
|
+
*/
|
|
139
166
|
function isSafeLocalIdentifier(name) {
|
|
140
167
|
return !isGlobalIdentifier(name) && isValidJsIdentifier(name);
|
|
141
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Checks if a name is a valid JavaScript identifier.
|
|
171
|
+
*
|
|
172
|
+
* Valid identifiers start with a letter, underscore, or dollar sign,
|
|
173
|
+
* followed by any combination of letters, digits, underscores, or dollar
|
|
174
|
+
* signs. Reserved keywords are not valid identifiers.
|
|
175
|
+
*
|
|
176
|
+
* @param name - The string to check
|
|
177
|
+
* @returns `true` if the name is a valid identifier
|
|
178
|
+
*/
|
|
142
179
|
function isValidJsIdentifier(name) {
|
|
143
180
|
return (name.length > 0 &&
|
|
144
181
|
!isJsKeyword(name) &&
|
|
145
182
|
/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name));
|
|
146
183
|
}
|
|
184
|
+
/**
|
|
185
|
+
* Converts a name to a valid namespace export identifier.
|
|
186
|
+
*
|
|
187
|
+
* If the name is a valid JavaScript identifier, it is returned as-is.
|
|
188
|
+
* Otherwise, it is returned as a quoted string for use in export statements
|
|
189
|
+
* like `export { foo as "unsafe-name" }`.
|
|
190
|
+
*
|
|
191
|
+
* @param name - The export name
|
|
192
|
+
* @returns The name as a valid export identifier
|
|
193
|
+
*/
|
|
147
194
|
function asNamespaceExport(name) {
|
|
148
195
|
return isValidJsIdentifier(name) ? name : JSON.stringify(name);
|
|
149
196
|
}
|
package/dist/ts-lang.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ts-lang.js","sourceRoot":"","sources":["../src/ts-lang.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"ts-lang.js","sourceRoot":"","sources":["../src/ts-lang.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Set of JavaScript reserved keywords and future reserved words.\n *\n * These identifiers cannot be used as variable or type names in generated code.\n *\n * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar}\n */\nconst JS_KEYWORDS = new Set([\n 'abstract',\n 'arguments',\n 'as',\n 'async',\n 'await',\n 'boolean',\n 'break',\n 'byte',\n 'case',\n 'catch',\n 'char',\n 'class',\n 'const',\n 'continue',\n 'debugger',\n 'default',\n 'delete',\n 'do',\n 'double',\n 'else',\n 'enum',\n 'eval',\n 'export',\n 'extends',\n 'false',\n 'final',\n 'finally',\n 'float',\n 'for',\n 'from',\n 'function',\n 'get',\n 'goto',\n 'if',\n 'implements',\n 'import',\n 'in',\n 'instanceof',\n 'int',\n 'interface',\n 'let',\n 'long',\n 'native',\n 'new',\n 'null',\n 'of',\n 'package',\n 'private',\n 'protected',\n 'public',\n 'return',\n 'set',\n 'short',\n 'static',\n 'super',\n 'switch',\n 'synchronized',\n 'this',\n 'throw',\n 'throws',\n 'transient',\n 'true',\n 'try',\n 'typeof',\n 'undefined',\n 'using',\n 'var',\n 'void',\n 'volatile',\n 'while',\n 'with',\n 'yield',\n])\n\n/**\n * Checks if a word is a JavaScript reserved keyword.\n *\n * @param word - The identifier to check\n * @returns `true` if the word is a reserved keyword\n */\nexport function isJsKeyword(word: string) {\n return JS_KEYWORDS.has(word)\n}\n\n// Only important to list var/type names that are likely to be used in the\n// generated code files.\nconst GLOBAL_IDENTIFIERS = new Set([\n // import { l } from \"@atproto/lex-schema\"\n 'l',\n // JS Globals\n 'self',\n 'globalThis',\n // ESM\n 'import',\n // CommonJS\n '__dirname',\n '__filename',\n 'require',\n 'module',\n 'exports',\n // TS Primitives\n 'any',\n 'bigint',\n 'boolean',\n 'declare',\n 'never',\n 'null',\n 'number',\n 'object',\n 'string',\n 'symbol',\n 'undefined',\n 'unknown',\n 'void',\n // TS Utility types\n 'Record',\n 'Partial',\n 'Readonly',\n 'Pick',\n 'Omit',\n 'Exclude',\n 'Extract',\n 'InstanceType',\n 'ReturnType',\n 'Required',\n 'ThisType',\n 'Uppercase',\n 'Lowercase',\n 'Capitalize',\n 'Uncapitalize',\n])\n\n/**\n * Checks if a word is a global identifier that should be avoided.\n *\n * This includes JavaScript globals, TypeScript built-in types, and\n * identifiers commonly used in the generated code.\n *\n * @param word - The identifier to check\n * @returns `true` if the word is a global identifier\n */\nexport function isGlobalIdentifier(word: string) {\n return GLOBAL_IDENTIFIERS.has(word)\n}\n\n/**\n * Checks if a name is safe to use as a local identifier.\n *\n * A safe local identifier is a valid JavaScript identifier that does not\n * conflict with global identifiers.\n *\n * @param name - The identifier to check\n * @returns `true` if the name is safe to use locally\n */\nexport function isSafeLocalIdentifier(name: string) {\n return !isGlobalIdentifier(name) && isValidJsIdentifier(name)\n}\n\n/**\n * Checks if a name is a valid JavaScript identifier.\n *\n * Valid identifiers start with a letter, underscore, or dollar sign,\n * followed by any combination of letters, digits, underscores, or dollar\n * signs. Reserved keywords are not valid identifiers.\n *\n * @param name - The string to check\n * @returns `true` if the name is a valid identifier\n */\nexport function isValidJsIdentifier(name: string) {\n return (\n name.length > 0 &&\n !isJsKeyword(name) &&\n /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)\n )\n}\n\n/**\n * Converts a name to a valid namespace export identifier.\n *\n * If the name is a valid JavaScript identifier, it is returned as-is.\n * Otherwise, it is returned as a quoted string for use in export statements\n * like `export { foo as \"unsafe-name\" }`.\n *\n * @param name - The export name\n * @returns The name as a valid export identifier\n */\nexport function asNamespaceExport(name: string) {\n return isValidJsIdentifier(name) ? name : JSON.stringify(name)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto/lex-builder",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.14",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "TypeScript schema builder for AT Lexicons",
|
|
6
6
|
"keywords": [
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"prettier": "^3.2.5",
|
|
40
40
|
"ts-morph": "^27.0.0",
|
|
41
41
|
"tslib": "^2.8.1",
|
|
42
|
-
"@atproto/lex-document": "^0.0.
|
|
43
|
-
"@atproto/lex-schema": "^0.0.
|
|
42
|
+
"@atproto/lex-document": "^0.0.13",
|
|
43
|
+
"@atproto/lex-schema": "^0.0.12"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
46
|
"@ts-morph/common": "^0.28.0",
|
package/src/filter.ts
CHANGED
|
@@ -1,10 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for building a filter function to include/exclude lexicon documents.
|
|
3
|
+
*/
|
|
1
4
|
export type BuildFilterOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Pattern(s) for lexicon NSIDs to include.
|
|
7
|
+
*
|
|
8
|
+
* Supports glob patterns with `*` as a wildcard that matches one or more
|
|
9
|
+
* characters. If not specified, all lexicons are included by default.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* { include: 'app.bsky.*' } // Include all app.bsky lexicons
|
|
14
|
+
* { include: ['com.atproto.*', 'app.bsky.*'] } // Include multiple patterns
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
2
17
|
include?: string | string[]
|
|
18
|
+
/**
|
|
19
|
+
* Pattern(s) for lexicon NSIDs to exclude.
|
|
20
|
+
*
|
|
21
|
+
* Supports glob patterns with `*` as a wildcard. Exclusions are applied
|
|
22
|
+
* after inclusions.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* { exclude: '*.internal.*' } // Exclude internal lexicons
|
|
27
|
+
* { exclude: ['*.test', '*.mock'] } // Exclude test and mock lexicons
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
3
30
|
exclude?: string | string[]
|
|
4
31
|
}
|
|
5
32
|
|
|
33
|
+
/**
|
|
34
|
+
* A function that tests whether a lexicon NSID should be included.
|
|
35
|
+
*
|
|
36
|
+
* @param input - The lexicon NSID to test
|
|
37
|
+
* @returns `true` if the NSID passes the filter, `false` otherwise
|
|
38
|
+
*/
|
|
6
39
|
export type Filter = (input: string) => boolean
|
|
7
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Builds a filter function from include/exclude patterns.
|
|
43
|
+
*
|
|
44
|
+
* The returned filter returns `true` for NSIDs that match any include pattern
|
|
45
|
+
* (or all NSIDs if no include patterns are specified) AND do not match any
|
|
46
|
+
* exclude pattern.
|
|
47
|
+
*
|
|
48
|
+
* @param options - The filter options containing include/exclude patterns
|
|
49
|
+
* @returns A filter function that can be applied to lexicon NSIDs
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* const filter = buildFilter({
|
|
54
|
+
* include: 'app.bsky.*',
|
|
55
|
+
* exclude: '*.internal.*',
|
|
56
|
+
* })
|
|
57
|
+
*
|
|
58
|
+
* filter('app.bsky.feed.post') // true
|
|
59
|
+
* filter('app.bsky.internal.foo') // false
|
|
60
|
+
* filter('com.atproto.repo') // false (not included)
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
8
63
|
export function buildFilter(options: BuildFilterOptions): Filter {
|
|
9
64
|
const include = createMatcher(options.include, () => true)
|
|
10
65
|
const exclude = createMatcher(options.exclude, () => false)
|
package/src/formatter.ts
CHANGED
|
@@ -12,16 +12,49 @@ const DEFAULT_BANNER = `/*
|
|
|
12
12
|
* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT.
|
|
13
13
|
*/`
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Options for configuring the code formatter.
|
|
17
|
+
*/
|
|
15
18
|
export type FormatterOptions = {
|
|
16
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Whether to format the generated code with Prettier.
|
|
21
|
+
*
|
|
22
|
+
* - `false`: No formatting (default)
|
|
23
|
+
* - `true`: Format with default Prettier options
|
|
24
|
+
* - `PrettierOptions`: Format with custom Prettier configuration
|
|
25
|
+
*
|
|
26
|
+
* @default false
|
|
27
|
+
*/
|
|
17
28
|
pretty?: boolean | PrettierOptions
|
|
29
|
+
/**
|
|
30
|
+
* A banner comment to prepend to each generated file.
|
|
31
|
+
*
|
|
32
|
+
* @default '/* THIS FILE WAS GENERATED BY "@atproto/lex". DO NOT EDIT. *\/'
|
|
33
|
+
*/
|
|
18
34
|
banner?: string
|
|
19
35
|
}
|
|
20
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Formats generated TypeScript code with optional Prettier formatting
|
|
39
|
+
* and banner comments.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* const formatter = new Formatter({ pretty: true })
|
|
44
|
+
* const formatted = await formatter.format(generatedCode)
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
21
47
|
export class Formatter {
|
|
48
|
+
/** The banner comment to prepend to formatted code. */
|
|
22
49
|
readonly banner: string
|
|
50
|
+
/** Prettier options, or `null` if formatting is disabled. */
|
|
23
51
|
readonly prettierOptions: PrettierOptions | null
|
|
24
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Creates a new Formatter instance.
|
|
55
|
+
*
|
|
56
|
+
* @param options - Formatting configuration options
|
|
57
|
+
*/
|
|
25
58
|
constructor(options: FormatterOptions = {}) {
|
|
26
59
|
this.banner = options?.banner ?? DEFAULT_BANNER
|
|
27
60
|
|
|
@@ -31,6 +64,14 @@ export class Formatter {
|
|
|
31
64
|
: options?.pretty || null
|
|
32
65
|
}
|
|
33
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Formats the given code string.
|
|
69
|
+
*
|
|
70
|
+
* Applies Prettier formatting if enabled, and prepends the banner comment.
|
|
71
|
+
*
|
|
72
|
+
* @param code - The TypeScript code to format
|
|
73
|
+
* @returns The formatted code with banner
|
|
74
|
+
*/
|
|
34
75
|
async format(code: string) {
|
|
35
76
|
const bannerPadding =
|
|
36
77
|
this.banner && !this.banner.endsWith('\n') ? '\n\n' : ''
|
package/src/index.ts
CHANGED
|
@@ -11,10 +11,43 @@ import {
|
|
|
11
11
|
export * from './lex-builder.js'
|
|
12
12
|
export * from './lexicon-directory-indexer.js'
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Combined options for building a TypeScript project from Lexicon documents.
|
|
16
|
+
*
|
|
17
|
+
* This type merges all configuration options needed for the complete build
|
|
18
|
+
* process, including builder configuration, loading options, and save options.
|
|
19
|
+
*
|
|
20
|
+
* @see {@link LexBuilderOptions} for builder configuration
|
|
21
|
+
* @see {@link LexBuilderLoadOptions} for lexicon loading options
|
|
22
|
+
* @see {@link LexBuilderSaveOptions} for output save options
|
|
23
|
+
*/
|
|
14
24
|
export type TsProjectBuildOptions = LexBuilderOptions &
|
|
15
25
|
LexBuilderLoadOptions &
|
|
16
26
|
LexBuilderSaveOptions
|
|
17
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Builds TypeScript schemas from Lexicon documents.
|
|
30
|
+
*
|
|
31
|
+
* This is the main entry point for programmatic usage of the lex-builder
|
|
32
|
+
* package. It creates a new {@link LexBuilder} instance, loads lexicon
|
|
33
|
+
* documents from the specified directory, and saves the generated TypeScript
|
|
34
|
+
* files to the output directory.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Combined build options including source directory, output
|
|
37
|
+
* directory, and generation settings
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* import { build } from '@atproto/lex-builder'
|
|
42
|
+
*
|
|
43
|
+
* await build({
|
|
44
|
+
* lexicons: './lexicons',
|
|
45
|
+
* out: './src/generated',
|
|
46
|
+
* pretty: true,
|
|
47
|
+
* clear: true,
|
|
48
|
+
* })
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
18
51
|
export async function build(options: TsProjectBuildOptions) {
|
|
19
52
|
const builder = new LexBuilder(options)
|
|
20
53
|
await builder.load(options)
|
package/src/lex-builder.ts
CHANGED
|
@@ -13,6 +13,14 @@ import {
|
|
|
13
13
|
} from './lexicon-directory-indexer.js'
|
|
14
14
|
import { asNamespaceExport } from './ts-lang.js'
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Configuration options for the {@link LexBuilder} class.
|
|
18
|
+
*
|
|
19
|
+
* Extends {@link LexDefBuilderOptions} with additional settings for
|
|
20
|
+
* controlling the generated TypeScript project structure.
|
|
21
|
+
*
|
|
22
|
+
* @see {@link LexDefBuilderOptions} for definition generation options
|
|
23
|
+
*/
|
|
16
24
|
export type LexBuilderOptions = LexDefBuilderOptions & {
|
|
17
25
|
/**
|
|
18
26
|
* Whether to generate an index file at the root exporting all top-level
|
|
@@ -22,19 +30,80 @@ export type LexBuilderOptions = LexDefBuilderOptions & {
|
|
|
22
30
|
* @default false
|
|
23
31
|
*/
|
|
24
32
|
indexFile?: boolean
|
|
33
|
+
/**
|
|
34
|
+
* The file extension to use for import specifiers in the generated code.
|
|
35
|
+
*
|
|
36
|
+
* @default '.js'
|
|
37
|
+
*/
|
|
25
38
|
importExt?: string
|
|
39
|
+
/**
|
|
40
|
+
* The file extension to use for generated TypeScript files.
|
|
41
|
+
*
|
|
42
|
+
* @default '.ts'
|
|
43
|
+
*/
|
|
26
44
|
fileExt?: string
|
|
27
45
|
}
|
|
28
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Options for loading lexicon documents into the builder.
|
|
49
|
+
*
|
|
50
|
+
* Combines directory indexing options with filtering options to control
|
|
51
|
+
* which lexicon documents are processed.
|
|
52
|
+
*
|
|
53
|
+
* @see {@link LexiconDirectoryIndexerOptions} for directory scanning options
|
|
54
|
+
* @see {@link BuildFilterOptions} for include/exclude filtering
|
|
55
|
+
*/
|
|
29
56
|
export type LexBuilderLoadOptions = LexiconDirectoryIndexerOptions &
|
|
30
57
|
BuildFilterOptions
|
|
31
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Options for saving generated TypeScript files.
|
|
61
|
+
*
|
|
62
|
+
* Combines formatting options with output directory configuration.
|
|
63
|
+
*/
|
|
32
64
|
export type LexBuilderSaveOptions = FormatterOptions & {
|
|
65
|
+
/**
|
|
66
|
+
* The output directory path where generated TypeScript files will be written.
|
|
67
|
+
*/
|
|
33
68
|
out: string
|
|
69
|
+
/**
|
|
70
|
+
* Whether to clear the output directory before writing files.
|
|
71
|
+
*
|
|
72
|
+
* When `true`, the entire output directory is deleted before writing new files.
|
|
73
|
+
*
|
|
74
|
+
* @default false
|
|
75
|
+
*/
|
|
34
76
|
clear?: boolean
|
|
77
|
+
/**
|
|
78
|
+
* Whether to allow overwriting existing files.
|
|
79
|
+
*
|
|
80
|
+
* When `false`, an error is thrown if any output file already exists.
|
|
81
|
+
*
|
|
82
|
+
* @default false
|
|
83
|
+
*/
|
|
35
84
|
override?: boolean
|
|
36
85
|
}
|
|
37
86
|
|
|
87
|
+
/**
|
|
88
|
+
* Main builder class for generating TypeScript schemas from Lexicon documents.
|
|
89
|
+
*
|
|
90
|
+
* The LexBuilder orchestrates the entire code generation process:
|
|
91
|
+
* 1. Loading and indexing lexicon documents from the filesystem
|
|
92
|
+
* 2. Generating TypeScript type definitions and runtime schemas
|
|
93
|
+
* 3. Creating namespace export trees for convenient imports
|
|
94
|
+
* 4. Saving formatted output files
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```ts
|
|
98
|
+
* const builder = new LexBuilder({ indexFile: true, pretty: true })
|
|
99
|
+
*
|
|
100
|
+
* // Load lexicons from a directory
|
|
101
|
+
* await builder.load({ lexicons: './lexicons' })
|
|
102
|
+
*
|
|
103
|
+
* // Save generated TypeScript to output directory
|
|
104
|
+
* await builder.save({ out: './src/generated', clear: true })
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
38
107
|
export class LexBuilder {
|
|
39
108
|
readonly #imported = new Set<string>()
|
|
40
109
|
readonly #project = new Project({
|
package/src/lex-def-builder.ts
CHANGED
|
@@ -38,14 +38,50 @@ import {
|
|
|
38
38
|
} from './ref-resolver.js'
|
|
39
39
|
import { asNamespaceExport } from './ts-lang.js'
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Configuration options for the {@link LexDefBuilder} class.
|
|
43
|
+
*
|
|
44
|
+
* @see {@link RefResolverOptions} for reference resolution options
|
|
45
|
+
*/
|
|
41
46
|
export type LexDefBuilderOptions = RefResolverOptions & {
|
|
47
|
+
/**
|
|
48
|
+
* The module specifier to use for importing the lexicon schema library.
|
|
49
|
+
*
|
|
50
|
+
* @default '@atproto/lex-schema'
|
|
51
|
+
*/
|
|
42
52
|
lib?: string
|
|
53
|
+
/**
|
|
54
|
+
* Whether to allow legacy blob references in the generated schemas.
|
|
55
|
+
*
|
|
56
|
+
* When `true`, blob types will accept both modern `BlobRef` and legacy
|
|
57
|
+
* `LegacyBlobRef` formats.
|
|
58
|
+
*
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
43
61
|
allowLegacyBlobs?: boolean
|
|
62
|
+
/**
|
|
63
|
+
* Whether to add `#__PURE__` annotations to function calls.
|
|
64
|
+
*
|
|
65
|
+
* These annotations help bundlers with tree-shaking by marking
|
|
66
|
+
* side-effect-free function calls.
|
|
67
|
+
*
|
|
68
|
+
* @default false
|
|
69
|
+
*/
|
|
44
70
|
pureAnnotations?: boolean
|
|
45
71
|
}
|
|
46
72
|
|
|
47
73
|
/**
|
|
48
|
-
*
|
|
74
|
+
* Builds TypeScript type definitions and runtime schemas from a single
|
|
75
|
+
* Lexicon document.
|
|
76
|
+
*
|
|
77
|
+
* This class is responsible for generating the `.defs.ts` files that contain:
|
|
78
|
+
* - Type aliases for each lexicon definition
|
|
79
|
+
* - Runtime schema validators using `@atproto/lex-schema`
|
|
80
|
+
* - Utility functions for type checking and validation
|
|
81
|
+
* - Proper import statements for cross-references
|
|
82
|
+
*
|
|
83
|
+
* Each lexicon definition type (record, object, query, procedure, etc.)
|
|
84
|
+
* is handled with specialized code generation logic.
|
|
49
85
|
*/
|
|
50
86
|
export class LexDefBuilder {
|
|
51
87
|
private readonly refResolver: RefResolver
|
|
@@ -6,8 +6,21 @@ import {
|
|
|
6
6
|
lexiconDocumentSchema,
|
|
7
7
|
} from '@atproto/lex-document'
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Options for the {@link LexiconDirectoryIndexer}.
|
|
11
|
+
*
|
|
12
|
+
* @see {@link ReadLexiconsOptions} for available options
|
|
13
|
+
*/
|
|
9
14
|
export type LexiconDirectoryIndexerOptions = ReadLexiconsOptions
|
|
10
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Indexes lexicon documents from a filesystem directory.
|
|
18
|
+
*
|
|
19
|
+
* This class recursively scans a directory for JSON files, parses them as
|
|
20
|
+
* lexicon documents, and provides an iterable interface for processing them.
|
|
21
|
+
* It extends {@link LexiconIterableIndexer} to support both iteration and
|
|
22
|
+
* lookup by NSID.
|
|
23
|
+
*/
|
|
11
24
|
export class LexiconDirectoryIndexer extends LexiconIterableIndexer {
|
|
12
25
|
constructor(options: LexiconDirectoryIndexerOptions) {
|
|
13
26
|
super(readLexicons(options))
|