@atproto/lex-builder 0.0.23 → 0.1.0-next.0

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.
@@ -1,12 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RefResolver = void 0;
4
- exports.getPublicIdentifiers = getPublicIdentifiers;
5
- const tslib_1 = require("tslib");
6
- const node_assert_1 = tslib_1.__importDefault(require("node:assert"));
7
- const node_path_1 = require("node:path");
8
- const ts_lang_js_1 = require("./ts-lang.js");
9
- const util_js_1 = require("./util.js");
1
+ import assert from 'node:assert';
2
+ import { join } from 'node:path';
3
+ import { isGlobalIdentifier, isJsKeyword, isSafeLocalIdentifier, isValidJsIdentifier, } from './ts-lang.js';
4
+ import { asRelativePath, memoize, startsWithLower, toCamelCase, toPascalCase, ucFirst, } from './util.js';
10
5
  /**
11
6
  * Resolves lexicon references to TypeScript identifiers.
12
7
  *
@@ -33,34 +28,139 @@ const util_js_1 = require("./util.js");
33
28
  * const external = await resolver.resolve('com.example.other#def')
34
29
  * ```
35
30
  */
36
- class RefResolver {
37
- doc;
38
- file;
39
- indexer;
40
- options;
31
+ export class RefResolver {
41
32
  constructor(doc, file, indexer, options) {
42
33
  this.doc = doc;
43
34
  this.file = file;
44
35
  this.indexer = indexer;
45
36
  this.options = options;
37
+ this.resolve = memoize(async (ref) => {
38
+ const [nsid, hash = 'main'] = ref.split('#');
39
+ if (nsid === '' || nsid === this.doc.id) {
40
+ return this.resolveLocal(hash);
41
+ }
42
+ else {
43
+ // @NOTE: Normalize (#main fragment) to ensure proper memoization
44
+ const fullRef = `${nsid}#${hash}`;
45
+ return this.resolveExternal(fullRef);
46
+ }
47
+ });
48
+ this.#defCounters = new Map();
49
+ /**
50
+ * Resolves a local definition hash to TypeScript identifiers.
51
+ *
52
+ * This method generates safe, non-conflicting identifiers for definitions
53
+ * within the current document. It handles edge cases like:
54
+ * - Hash names that are JavaScript keywords
55
+ * - Hash names that conflict with global identifiers
56
+ * - Multiple hashes that would produce the same identifier
57
+ *
58
+ * @param hash - The definition hash (e.g., 'main', 'record', 'myType')
59
+ * @returns A promise resolving to the TypeScript identifiers
60
+ * @throws Error if the hash does not exist in the document
61
+ * @throws Error if conflicting type names are detected
62
+ *
63
+ * @note The returned `typeName` and `varName` are *both* guaranteed to be
64
+ * valid TypeScript identifiers.
65
+ */
66
+ this.resolveLocal = memoize(async (hash) => {
67
+ const hashes = Object.keys(this.doc.defs);
68
+ if (!hashes.includes(hash)) {
69
+ throw new Error(`Definition ${hash} not found in ${this.doc.id}`);
70
+ }
71
+ // Because we are using predictable "public" identifiers for type names,
72
+ // we need to ensure there are no conflicts between different definitions
73
+ // in the same lexicon document.
74
+ //
75
+ // @NOTE It should be possible to implement a way to generate
76
+ // non-conflicting type names for all public (type) identifiers in a
77
+ // project. However, this would add a lot of complexity to the code
78
+ // generation process, and the likelihood of such conflicts happening in
79
+ // practice is very low, so we opt for a simpler approach of just throwing
80
+ // an error if a conflict is detected.
81
+ const pub = getPublicIdentifiers(hash);
82
+ for (const otherHash of hashes) {
83
+ if (otherHash === hash)
84
+ continue;
85
+ const otherPub = getPublicIdentifiers(otherHash);
86
+ if (otherPub.typeName === pub.typeName) {
87
+ throw new Error(`Conflicting type names for definitions #${hash} and #${otherHash} in ${this.doc.id}`);
88
+ }
89
+ }
90
+ // Try to keep and identifier that resembles the original hash as identifier
91
+ const safeIdentifier = asSafeDefinitionIdentifier(hash);
92
+ // If the safe identifier is not conflicting with other definition names,
93
+ // or reserved words, we can use it as-is. Otherwise, we need to generate
94
+ // a unique safe identifier.
95
+ const varName = safeIdentifier
96
+ ? !hashes.some((otherHash) => {
97
+ if (otherHash === hash)
98
+ return false;
99
+ const otherIdentifier = asSafeDefinitionIdentifier(otherHash);
100
+ return otherIdentifier === safeIdentifier;
101
+ })
102
+ ? // Safe identifier can be used as-is as it does not conflict with
103
+ // other definition names
104
+ safeIdentifier
105
+ : // In order to keep identifiers stable, we use the safe identifier
106
+ // as base, and append a counter to avoid conflicts
107
+ this.nextSafeDefinitionIdentifier(safeIdentifier)
108
+ : // hash only contained unsafe characters, generate a safe one
109
+ this.nextSafeDefinitionIdentifier(hash);
110
+ const typeName = ucFirst(varName);
111
+ assert(isSafeLocalIdentifier(typeName), 'Expected safe type identifier');
112
+ assert(varName !== typeName, 'Variable and type name should be different');
113
+ return { varName, typeName };
114
+ });
115
+ /**
116
+ * @note Since this is a memoized function, and is used to generate the name
117
+ * of local variables, we should avoid returning different results for
118
+ * similar, but non strictly equal, inputs (eg. normalized / non-normalized).
119
+ * @see {@link resolve}
120
+ */
121
+ this.resolveExternal = memoize(async (fullRef) => {
122
+ const [nsid, hash] = fullRef.split('#');
123
+ const moduleSpecifier = this.options.moduleSpecifier
124
+ ? this.options.moduleSpecifier(nsid)
125
+ : `${asRelativePath(this.file.getDirectoryPath(), join('/', ...nsid.split('.')))}.defs${this.options.importExt ?? '.js'}`;
126
+ // Lets first make sure the referenced lexicon exists
127
+ const srcDoc = await this.indexer.get(nsid);
128
+ const srcDef = Object.hasOwn(srcDoc.defs, hash) ? srcDoc.defs[hash] : null;
129
+ if (!srcDef) {
130
+ throw new Error(`Missing def "${hash}" in "${nsid}" (referenced from ${this.doc.id})`);
131
+ }
132
+ const publicIds = getPublicIdentifiers(hash);
133
+ if (!isValidJsIdentifier(publicIds.typeName)) {
134
+ // If <typeName> is not a valid identifier, we cannot access the type
135
+ // using dot notation (<nsIdentifier>.<typeName>). Note that, unlike js
136
+ // variables, types cannot be accessed using string indexing (like:
137
+ // <nsIdentifier>['<typeName>']) because it generates TypeScript errors:
138
+ //
139
+ // > "Cannot use namespace '<nsIdentifier>' as a type."
140
+ // Instead the generated code should look like:
141
+ // import { "<unsafeTypeName>" as <safeIdentifier> } from './<moduleSpecifier>.js'
142
+ // Because it requires more complex management of local variables names,
143
+ // and we don't expect this to actually happen with properly designed
144
+ // lexicons documents, we do not support this for now.
145
+ throw new Error('Import of definitions with unsafe type names is not supported');
146
+ }
147
+ // import * as <nsIdentifier> from './<moduleSpecifier>.js'
148
+ const nsIdentifier = this.getNsIdentifier(nsid, moduleSpecifier);
149
+ return {
150
+ varName: isValidJsIdentifier(publicIds.varName)
151
+ ? `${nsIdentifier}.${publicIds.varName}`
152
+ : `${nsIdentifier}[${JSON.stringify(publicIds.varName)}]`,
153
+ typeName: `${nsIdentifier}.${publicIds.typeName}`,
154
+ };
155
+ });
156
+ this.#nsIdentifiersCounters = new Map();
46
157
  }
47
- resolve = (0, util_js_1.memoize)(async (ref) => {
48
- const [nsid, hash = 'main'] = ref.split('#');
49
- if (nsid === '' || nsid === this.doc.id) {
50
- return this.resolveLocal(hash);
51
- }
52
- else {
53
- // @NOTE: Normalize (#main fragment) to ensure proper memoization
54
- const fullRef = `${nsid}#${hash}`;
55
- return this.resolveExternal(fullRef);
56
- }
57
- });
58
- #defCounters = new Map();
158
+ #defCounters;
59
159
  nextSafeDefinitionIdentifier(name) {
60
160
  // use camelCase version of the hash as base name
61
- const nameSafe = (0, util_js_1.startsWithLower)(name) && (0, ts_lang_js_1.isValidJsIdentifier)(name)
161
+ const nameSafe = startsWithLower(name) && isValidJsIdentifier(name)
62
162
  ? name
63
- : (0, util_js_1.toCamelCase)(name).replace(/^[0-9]+/g, '') || 'def';
163
+ : toCamelCase(name).replace(/^[0-9]+/g, '') || 'def';
64
164
  const count = this.#defCounters.get(nameSafe) ?? 0;
65
165
  this.#defCounters.set(nameSafe, count + 1);
66
166
  // @NOTE We don't need to check against local declarations in the file here
@@ -68,116 +168,9 @@ class RefResolver {
68
168
  // identifier has a <nameSafe>$<number> format ("$" cannot appear in
69
169
  // hashes so only *we* are generating such identifiers).
70
170
  const identifier = `${nameSafe}$${count}`;
71
- (0, node_assert_1.default)((0, ts_lang_js_1.isValidJsIdentifier)(identifier), `Unable to generate safe identifier for: "${name}"`);
171
+ assert(isValidJsIdentifier(identifier), `Unable to generate safe identifier for: "${name}"`);
72
172
  return identifier;
73
173
  }
74
- /**
75
- * Resolves a local definition hash to TypeScript identifiers.
76
- *
77
- * This method generates safe, non-conflicting identifiers for definitions
78
- * within the current document. It handles edge cases like:
79
- * - Hash names that are JavaScript keywords
80
- * - Hash names that conflict with global identifiers
81
- * - Multiple hashes that would produce the same identifier
82
- *
83
- * @param hash - The definition hash (e.g., 'main', 'record', 'myType')
84
- * @returns A promise resolving to the TypeScript identifiers
85
- * @throws Error if the hash does not exist in the document
86
- * @throws Error if conflicting type names are detected
87
- *
88
- * @note The returned `typeName` and `varName` are *both* guaranteed to be
89
- * valid TypeScript identifiers.
90
- */
91
- resolveLocal = (0, util_js_1.memoize)(async (hash) => {
92
- const hashes = Object.keys(this.doc.defs);
93
- if (!hashes.includes(hash)) {
94
- throw new Error(`Definition ${hash} not found in ${this.doc.id}`);
95
- }
96
- // Because we are using predictable "public" identifiers for type names,
97
- // we need to ensure there are no conflicts between different definitions
98
- // in the same lexicon document.
99
- //
100
- // @NOTE It should be possible to implement a way to generate
101
- // non-conflicting type names for all public (type) identifiers in a
102
- // project. However, this would add a lot of complexity to the code
103
- // generation process, and the likelihood of such conflicts happening in
104
- // practice is very low, so we opt for a simpler approach of just throwing
105
- // an error if a conflict is detected.
106
- const pub = getPublicIdentifiers(hash);
107
- for (const otherHash of hashes) {
108
- if (otherHash === hash)
109
- continue;
110
- const otherPub = getPublicIdentifiers(otherHash);
111
- if (otherPub.typeName === pub.typeName) {
112
- throw new Error(`Conflicting type names for definitions #${hash} and #${otherHash} in ${this.doc.id}`);
113
- }
114
- }
115
- // Try to keep and identifier that resembles the original hash as identifier
116
- const safeIdentifier = asSafeDefinitionIdentifier(hash);
117
- // If the safe identifier is not conflicting with other definition names,
118
- // or reserved words, we can use it as-is. Otherwise, we need to generate
119
- // a unique safe identifier.
120
- const varName = safeIdentifier
121
- ? !hashes.some((otherHash) => {
122
- if (otherHash === hash)
123
- return false;
124
- const otherIdentifier = asSafeDefinitionIdentifier(otherHash);
125
- return otherIdentifier === safeIdentifier;
126
- })
127
- ? // Safe identifier can be used as-is as it does not conflict with
128
- // other definition names
129
- safeIdentifier
130
- : // In order to keep identifiers stable, we use the safe identifier
131
- // as base, and append a counter to avoid conflicts
132
- this.nextSafeDefinitionIdentifier(safeIdentifier)
133
- : // hash only contained unsafe characters, generate a safe one
134
- this.nextSafeDefinitionIdentifier(hash);
135
- const typeName = (0, util_js_1.ucFirst)(varName);
136
- (0, node_assert_1.default)((0, ts_lang_js_1.isSafeLocalIdentifier)(typeName), 'Expected safe type identifier');
137
- (0, node_assert_1.default)(varName !== typeName, 'Variable and type name should be different');
138
- return { varName, typeName };
139
- });
140
- /**
141
- * @note Since this is a memoized function, and is used to generate the name
142
- * of local variables, we should avoid returning different results for
143
- * similar, but non strictly equal, inputs (eg. normalized / non-normalized).
144
- * @see {@link resolve}
145
- */
146
- resolveExternal = (0, util_js_1.memoize)(async (fullRef) => {
147
- const [nsid, hash] = fullRef.split('#');
148
- const moduleSpecifier = this.options.moduleSpecifier
149
- ? this.options.moduleSpecifier(nsid)
150
- : `${(0, util_js_1.asRelativePath)(this.file.getDirectoryPath(), (0, node_path_1.join)('/', ...nsid.split('.')))}.defs${this.options.importExt ?? '.js'}`;
151
- // Lets first make sure the referenced lexicon exists
152
- const srcDoc = await this.indexer.get(nsid);
153
- const srcDef = Object.hasOwn(srcDoc.defs, hash) ? srcDoc.defs[hash] : null;
154
- if (!srcDef) {
155
- throw new Error(`Missing def "${hash}" in "${nsid}" (referenced from ${this.doc.id})`);
156
- }
157
- const publicIds = getPublicIdentifiers(hash);
158
- if (!(0, ts_lang_js_1.isValidJsIdentifier)(publicIds.typeName)) {
159
- // If <typeName> is not a valid identifier, we cannot access the type
160
- // using dot notation (<nsIdentifier>.<typeName>). Note that, unlike js
161
- // variables, types cannot be accessed using string indexing (like:
162
- // <nsIdentifier>['<typeName>']) because it generates TypeScript errors:
163
- //
164
- // > "Cannot use namespace '<nsIdentifier>' as a type."
165
- // Instead the generated code should look like:
166
- // import { "<unsafeTypeName>" as <safeIdentifier> } from './<moduleSpecifier>'
167
- // Because it requires more complex management of local variables names,
168
- // and we don't expect this to actually happen with properly designed
169
- // lexicons documents, we do not support this for now.
170
- throw new Error('Import of definitions with unsafe type names is not supported');
171
- }
172
- // import * as <nsIdentifier> from './<moduleSpecifier>'
173
- const nsIdentifier = this.getNsIdentifier(nsid, moduleSpecifier);
174
- return {
175
- varName: (0, ts_lang_js_1.isValidJsIdentifier)(publicIds.varName)
176
- ? `${nsIdentifier}.${publicIds.varName}`
177
- : `${nsIdentifier}[${JSON.stringify(publicIds.varName)}]`,
178
- typeName: `${nsIdentifier}.${publicIds.typeName}`,
179
- };
180
- });
181
174
  getNsIdentifier(nsid, moduleSpecifier) {
182
175
  const namespaceImportDeclaration = this.file.getImportDeclaration((imp) => !imp.isTypeOnly() &&
183
176
  imp.getModuleSpecifierValue() === moduleSpecifier &&
@@ -188,7 +181,7 @@ class RefResolver {
188
181
  });
189
182
  return namespaceImportDeclaration.getNamespaceImport().getText();
190
183
  }
191
- #nsIdentifiersCounters = new Map();
184
+ #nsIdentifiersCounters;
192
185
  computeSafeNamespaceIdentifierFor(nsid) {
193
186
  const baseName = nsidToIdentifier(nsid) || 'NS';
194
187
  let name = baseName;
@@ -207,7 +200,7 @@ class RefResolver {
207
200
  this.conflictsWithImports(name));
208
201
  }
209
202
  conflictsWithKeywords(name) {
210
- return (0, ts_lang_js_1.isJsKeyword)(name) || (0, ts_lang_js_1.isGlobalIdentifier)(name);
203
+ return isJsKeyword(name) || isGlobalIdentifier(name);
211
204
  }
212
205
  conflictsWithUtils(name) {
213
206
  // Do not allow "Main" as imported ns identifier since it has a special
@@ -224,7 +217,7 @@ class RefResolver {
224
217
  }
225
218
  conflictsWithLocalDefs(name) {
226
219
  return Object.keys(this.doc.defs).some((hash) => {
227
- const identifier = (0, util_js_1.toCamelCase)(hash);
220
+ const identifier = toCamelCase(hash);
228
221
  // A safe identifier will be generated, no risk of conflict.
229
222
  if (!identifier)
230
223
  return false;
@@ -232,7 +225,7 @@ class RefResolver {
232
225
  if (identifier === name || `_${identifier}` === name)
233
226
  return true;
234
227
  // The imported name conflicts with the type name of a local definition
235
- const typeName = (0, util_js_1.ucFirst)(identifier);
228
+ const typeName = ucFirst(identifier);
236
229
  if (typeName === name || `_${typeName}` === name)
237
230
  return true;
238
231
  return false;
@@ -261,7 +254,6 @@ class RefResolver {
261
254
  (named.getAliasNode()?.getText() ?? named.getName()) === name));
262
255
  }
263
256
  }
264
- exports.RefResolver = RefResolver;
265
257
  /**
266
258
  * @see {@link https://atproto.com/specs/nsid NSID syntax spec}
267
259
  */
@@ -272,8 +264,8 @@ function nsidToIdentifier(nsid) {
272
264
  // because they start with a digit), try with more segments until we reach the
273
265
  // full NSID.
274
266
  for (let i = 2; i < parts.length; i++) {
275
- const identifier = (0, util_js_1.toPascalCase)(parts.slice(-i).join('.'));
276
- if ((0, ts_lang_js_1.isSafeLocalIdentifier)(identifier))
267
+ const identifier = toPascalCase(parts.slice(-i).join('.'));
268
+ if (isSafeLocalIdentifier(identifier))
277
269
  return identifier;
278
270
  }
279
271
  return undefined;
@@ -292,26 +284,26 @@ function nsidToIdentifier(nsid) {
292
284
  * identifier. `varName` may not be a valid identifier (eg. if the hash contains
293
285
  * unsafe characters), and may need to be accessed using string indexing.
294
286
  */
295
- function getPublicIdentifiers(hash) {
287
+ export function getPublicIdentifiers(hash) {
296
288
  const varName = hash;
297
289
  // @NOTE we try to circumvent the issue of unsafe type names described in
298
290
  // `RefResolver.resolveExternal` by ensuring that type names are always safe
299
291
  // identifiers, even if it means changing them from the original hash.
300
- let typeName = (0, util_js_1.toPascalCase)(hash);
301
- if (varName === typeName || !(0, ts_lang_js_1.isValidJsIdentifier)(typeName)) {
292
+ let typeName = toPascalCase(hash);
293
+ if (varName === typeName || !isValidJsIdentifier(typeName)) {
302
294
  typeName = `TypeOf${typeName}`;
303
295
  }
304
- (0, node_assert_1.default)((0, ts_lang_js_1.isValidJsIdentifier)(typeName), `Unable to generate a predictable safe identifier for "${hash}"`);
296
+ assert(isValidJsIdentifier(typeName), `Unable to generate a predictable safe identifier for "${hash}"`);
305
297
  return { varName, typeName };
306
298
  }
307
299
  function asSafeDefinitionIdentifier(name) {
308
- if ((0, util_js_1.startsWithLower)(name) &&
309
- (0, ts_lang_js_1.isSafeLocalIdentifier)(name) &&
310
- (0, ts_lang_js_1.isSafeLocalIdentifier)((0, util_js_1.ucFirst)(name))) {
300
+ if (startsWithLower(name) &&
301
+ isSafeLocalIdentifier(name) &&
302
+ isSafeLocalIdentifier(ucFirst(name))) {
311
303
  return name;
312
304
  }
313
- const camel = (0, util_js_1.toCamelCase)(name);
314
- if ((0, ts_lang_js_1.isSafeLocalIdentifier)(camel) && (0, ts_lang_js_1.isSafeLocalIdentifier)((0, util_js_1.ucFirst)(camel))) {
305
+ const camel = toCamelCase(name);
306
+ if (isSafeLocalIdentifier(camel) && isSafeLocalIdentifier(ucFirst(camel))) {
315
307
  return camel;
316
308
  }
317
309
  return undefined;
@@ -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\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 moduleSpecifier?: (nsid: string) => 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 = this.options.moduleSpecifier\n ? this.options.moduleSpecifier(nsid)\n : `${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"]}
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 moduleSpecifier?: (nsid: string) => 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 = this.options.moduleSpecifier\n ? this.options.moduleSpecifier(nsid)\n : `${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>.js'\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>.js'\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.js CHANGED
@@ -1,10 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isJsKeyword = isJsKeyword;
4
- exports.isGlobalIdentifier = isGlobalIdentifier;
5
- exports.isSafeLocalIdentifier = isSafeLocalIdentifier;
6
- exports.isValidJsIdentifier = isValidJsIdentifier;
7
- exports.asNamespaceExport = asNamespaceExport;
8
1
  /**
9
2
  * Set of JavaScript reserved keywords and future reserved words.
10
3
  *
@@ -92,7 +85,7 @@ const JS_KEYWORDS = new Set([
92
85
  * @param word - The identifier to check
93
86
  * @returns `true` if the word is a reserved keyword
94
87
  */
95
- function isJsKeyword(word) {
88
+ export function isJsKeyword(word) {
96
89
  return JS_KEYWORDS.has(word);
97
90
  }
98
91
  // Only important to list var/type names that are likely to be used in the
@@ -151,7 +144,7 @@ const GLOBAL_IDENTIFIERS = new Set([
151
144
  * @param word - The identifier to check
152
145
  * @returns `true` if the word is a global identifier
153
146
  */
154
- function isGlobalIdentifier(word) {
147
+ export function isGlobalIdentifier(word) {
155
148
  return GLOBAL_IDENTIFIERS.has(word);
156
149
  }
157
150
  /**
@@ -163,7 +156,7 @@ function isGlobalIdentifier(word) {
163
156
  * @param name - The identifier to check
164
157
  * @returns `true` if the name is safe to use locally
165
158
  */
166
- function isSafeLocalIdentifier(name) {
159
+ export function isSafeLocalIdentifier(name) {
167
160
  return !isGlobalIdentifier(name) && isValidJsIdentifier(name);
168
161
  }
169
162
  /**
@@ -176,7 +169,7 @@ function isSafeLocalIdentifier(name) {
176
169
  * @param name - The string to check
177
170
  * @returns `true` if the name is a valid identifier
178
171
  */
179
- function isValidJsIdentifier(name) {
172
+ export function isValidJsIdentifier(name) {
180
173
  return (name.length > 0 &&
181
174
  !isJsKeyword(name) &&
182
175
  /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name));
@@ -191,7 +184,7 @@ function isValidJsIdentifier(name) {
191
184
  * @param name - The export name
192
185
  * @returns The name as a valid export identifier
193
186
  */
194
- function asNamespaceExport(name) {
187
+ export function asNamespaceExport(name) {
195
188
  return isValidJsIdentifier(name) ? name : JSON.stringify(name);
196
189
  }
197
190
  //# sourceMappingURL=ts-lang.js.map
@@ -1 +1 @@
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"]}
1
+ {"version":3,"file":"ts-lang.js","sourceRoot":"","sources":["../src/ts-lang.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,WAAW,GAAG,IAAI,GAAG,CAAC;IAC1B,UAAU;IACV,WAAW;IACX,IAAI;IACJ,OAAO;IACP,OAAO;IACP,SAAS;IACT,OAAO;IACP,MAAM;IACN,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;IACP,OAAO;IACP,UAAU;IACV,UAAU;IACV,SAAS;IACT,QAAQ;IACR,IAAI;IACJ,QAAQ;IACR,MAAM;IACN,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,OAAO;IACP,OAAO;IACP,SAAS;IACT,OAAO;IACP,KAAK;IACL,MAAM;IACN,UAAU;IACV,KAAK;IACL,MAAM;IACN,IAAI;IACJ,YAAY;IACZ,QAAQ;IACR,IAAI;IACJ,YAAY;IACZ,KAAK;IACL,WAAW;IACX,KAAK;IACL,MAAM;IACN,QAAQ;IACR,KAAK;IACL,MAAM;IACN,IAAI;IACJ,SAAS;IACT,SAAS;IACT,WAAW;IACX,QAAQ;IACR,QAAQ;IACR,KAAK;IACL,OAAO;IACP,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,cAAc;IACd,MAAM;IACN,OAAO;IACP,QAAQ;IACR,WAAW;IACX,MAAM;IACN,KAAK;IACL,QAAQ;IACR,WAAW;IACX,OAAO;IACP,KAAK;IACL,MAAM;IACN,UAAU;IACV,OAAO;IACP,MAAM;IACN,OAAO;CACR,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,OAAO,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AAC9B,CAAC;AAED,0EAA0E;AAC1E,wBAAwB;AACxB,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,0CAA0C;IAC1C,GAAG;IACH,aAAa;IACb,MAAM;IACN,YAAY;IACZ,MAAM;IACN,QAAQ;IACR,WAAW;IACX,WAAW;IACX,YAAY;IACZ,SAAS;IACT,QAAQ;IACR,SAAS;IACT,gBAAgB;IAChB,KAAK;IACL,QAAQ;IACR,SAAS;IACT,SAAS;IACT,OAAO;IACP,MAAM;IACN,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,WAAW;IACX,SAAS;IACT,MAAM;IACN,mBAAmB;IACnB,QAAQ;IACR,SAAS;IACT,UAAU;IACV,MAAM;IACN,MAAM;IACN,SAAS;IACT,SAAS;IACT,cAAc;IACd,YAAY;IACZ,UAAU;IACV,UAAU;IACV,WAAW;IACX,WAAW;IACX,YAAY;IACZ,cAAc;CACf,CAAC,CAAA;AAEF;;;;;;;;GAQG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;AACrC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY;IAChD,OAAO,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,mBAAmB,CAAC,IAAI,CAAC,CAAA;AAC/D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,OAAO,CACL,IAAI,CAAC,MAAM,GAAG,CAAC;QACf,CAAC,WAAW,CAAC,IAAI,CAAC;QAClB,4BAA4B,CAAC,IAAI,CAAC,IAAI,CAAC,CACxC,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,OAAO,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;AAChE,CAAC","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"]}