p2s_mapper 1.3.0 → 2.0.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.
Files changed (94) hide show
  1. package/README.md +214 -32
  2. package/dist/alphaFoldModels.d.ts +4 -0
  3. package/dist/alphaFoldModels.js +4 -0
  4. package/dist/alphaFoldModels.js.map +1 -1
  5. package/dist/coordinates.d.ts +8 -6
  6. package/dist/coordinates.js.map +1 -1
  7. package/dist/dbIds.d.ts +1 -2
  8. package/dist/dbIds.js +5 -1
  9. package/dist/dbIds.js.map +1 -1
  10. package/dist/extractPerResidueConfidence.d.ts +1 -2
  11. package/dist/extractStructureSequences.d.ts +14 -15
  12. package/dist/fetchUtils.js +19 -1
  13. package/dist/fetchUtils.js.map +1 -1
  14. package/dist/index.d.ts +1 -3
  15. package/dist/index.js +2 -4
  16. package/dist/index.js.map +1 -1
  17. package/dist/isoformRanking.d.ts +3 -0
  18. package/dist/pairwiseAlignment.d.ts +1 -2
  19. package/dist/pdbEntry.d.ts +32 -0
  20. package/dist/pdbEntry.js +182 -0
  21. package/dist/pdbEntry.js.map +1 -0
  22. package/dist/pdbUniProtMapping.d.ts +9 -33
  23. package/dist/pdbUniProtMapping.js +17 -134
  24. package/dist/pdbUniProtMapping.js.map +1 -1
  25. package/dist/structureSources.d.ts +37 -13
  26. package/dist/structureSources.js +144 -57
  27. package/dist/structureSources.js.map +1 -1
  28. package/dist/structureUrls.d.ts +2 -0
  29. package/dist/structureUrls.js +11 -0
  30. package/dist/structureUrls.js.map +1 -1
  31. package/esm/alphaFoldModels.d.ts +4 -0
  32. package/esm/alphaFoldModels.js +4 -0
  33. package/esm/alphaFoldModels.js.map +1 -1
  34. package/esm/coordinates.d.ts +8 -6
  35. package/esm/coordinates.js.map +1 -1
  36. package/esm/dbIds.d.ts +1 -2
  37. package/esm/dbIds.js +5 -1
  38. package/esm/dbIds.js.map +1 -1
  39. package/esm/extractPerResidueConfidence.d.ts +1 -2
  40. package/esm/extractStructureSequences.d.ts +14 -15
  41. package/esm/fetchUtils.js +19 -1
  42. package/esm/fetchUtils.js.map +1 -1
  43. package/esm/index.d.ts +1 -3
  44. package/esm/index.js +2 -4
  45. package/esm/index.js.map +1 -1
  46. package/esm/isoformRanking.d.ts +3 -0
  47. package/esm/pairwiseAlignment.d.ts +1 -2
  48. package/esm/pdbEntry.d.ts +32 -0
  49. package/esm/pdbEntry.js +174 -0
  50. package/esm/pdbEntry.js.map +1 -0
  51. package/esm/pdbUniProtMapping.d.ts +9 -33
  52. package/esm/pdbUniProtMapping.js +17 -131
  53. package/esm/pdbUniProtMapping.js.map +1 -1
  54. package/esm/structureSources.d.ts +37 -13
  55. package/esm/structureSources.js +139 -55
  56. package/esm/structureSources.js.map +1 -1
  57. package/esm/structureUrls.d.ts +2 -0
  58. package/esm/structureUrls.js +9 -0
  59. package/esm/structureUrls.js.map +1 -1
  60. package/package.json +3 -2
  61. package/src/alphaFoldModels.ts +12 -0
  62. package/src/coordinates.ts +3 -4
  63. package/src/dbIds.ts +6 -2
  64. package/src/extractPerResidueConfidence.ts +1 -1
  65. package/src/extractStructureSequences.ts +14 -14
  66. package/src/fetchUtils.ts +23 -1
  67. package/src/index.ts +2 -4
  68. package/src/isoformRanking.ts +3 -3
  69. package/src/pairwiseAlignment.ts +1 -1
  70. package/src/pdbEntry.ts +240 -0
  71. package/src/pdbUniProtMapping.ts +20 -158
  72. package/src/structureSources.ts +194 -70
  73. package/src/structureUrls.ts +11 -0
  74. package/dist/pdbeBestStructures.d.ts +0 -23
  75. package/dist/pdbeBestStructures.js +0 -79
  76. package/dist/pdbeBestStructures.js.map +0 -1
  77. package/dist/pdbeEntryMolecules.d.ts +0 -7
  78. package/dist/pdbeEntryMolecules.js +0 -39
  79. package/dist/pdbeEntryMolecules.js.map +0 -1
  80. package/dist/siftsNumbering.d.ts +0 -23
  81. package/dist/siftsNumbering.js +0 -44
  82. package/dist/siftsNumbering.js.map +0 -1
  83. package/esm/pdbeBestStructures.d.ts +0 -23
  84. package/esm/pdbeBestStructures.js +0 -73
  85. package/esm/pdbeBestStructures.js.map +0 -1
  86. package/esm/pdbeEntryMolecules.d.ts +0 -7
  87. package/esm/pdbeEntryMolecules.js +0 -35
  88. package/esm/pdbeEntryMolecules.js.map +0 -1
  89. package/esm/siftsNumbering.d.ts +0 -23
  90. package/esm/siftsNumbering.js +0 -40
  91. package/esm/siftsNumbering.js.map +0 -1
  92. package/src/pdbeBestStructures.ts +0 -94
  93. package/src/pdbeEntryMolecules.ts +0 -37
  94. package/src/siftsNumbering.ts +0 -61
@@ -1,16 +1,21 @@
1
- // The experimental structures 3D-Beacons knows for a UniProt entry: every PDBe
2
- // entry mapped to the accession, with the UniProt range it covers and its
3
- // resolution, in one CORS-enabled call. That is what lets a page offer a
4
- // crystal structure beside a prediction without a PDB search of its own; take
5
- // the entry id from here and ask `fetchUniProtStructureMappings` for its
1
+ // The experimental structures RCSB aligns to a UniProt entry: every PDB entity
2
+ // carrying the accession, with the UniProt range it covers, then each entry's
3
+ // method, resolution and chains. Two CORS-enabled calls, which is what lets a
4
+ // page offer a crystal structure beside a prediction without a PDB search of
5
+ // its own; take the entry id from here and ask `fetchPdbEntry` for its
6
6
  // residue mapping.
7
7
 
8
- import { rawfetch } from './fetchUtils.ts'
8
+ import { RCSB_DATA_GRAPHQL_URL, graphqlFetch } from './pdbEntry.ts'
9
+ import { isPdbId } from './structureUrls.ts'
9
10
 
10
11
  import type { FetchOptions } from './fetchUtils.ts'
11
12
 
13
+ export const RCSB_SEQUENCE_COORDINATES_URL =
14
+ 'https://sequence-coordinates.rcsb.org/graphql'
15
+
12
16
  export interface ExperimentalStructure {
13
17
  pdbId: string
18
+ /** RCSB's name for it: 'X-ray', 'EM', 'NMR', ... */
14
19
  method: string
15
20
  /** Å; absent for NMR and some EM entries */
16
21
  resolution?: number
@@ -19,15 +24,45 @@ export interface ExperimentalStructure {
19
24
  end: number
20
25
  /** fraction of the UniProt sequence the entry covers, 0..1 */
21
26
  coverage: number
27
+ /** author chain ids carrying the protein */
28
+ chains: string[]
22
29
  }
23
30
 
24
- // Unfiltered: the `?provider=pdbe` form answered BRAF, HBB and BRCA2 with a 404
25
- // about one time in three (2026-10-09), and the parser keeps PDBe's entries
26
- // anyway.
27
- export function beaconsSummaryUrl(uniprotId: string) {
28
- return `https://www.ebi.ac.uk/pdbe/pdbe-kb/3dbeacons/api/uniprot/summary/${encodeURIComponent(uniprotId)}.json`
31
+ /** One polymer entity aligned to the accession. */
32
+ export interface EntityAlignment {
33
+ /** RCSB's id, "1TUP_3" */
34
+ entityId: string
35
+ pdbId: string
36
+ start: number
37
+ end: number
38
+ coverage: number
29
39
  }
30
40
 
41
+ export interface EntityDetails {
42
+ method: string
43
+ resolution?: number
44
+ chains: string[]
45
+ }
46
+
47
+ export function entityAlignmentsQuery(uniprotId: string) {
48
+ return `{ alignments(from: UNIPROT, to: PDB_ENTITY, queryId: "${uniprotId}") {
49
+ target_alignments {
50
+ target_id
51
+ coverage { query_coverage }
52
+ aligned_regions { query_begin query_end }
53
+ }
54
+ } }`
55
+ }
56
+
57
+ export const ENTITY_DETAILS_QUERY = `query($ids: [String!]!) { polymer_entities(entity_ids: $ids) {
58
+ rcsb_id
59
+ rcsb_polymer_entity_container_identifiers { auth_asym_ids }
60
+ entry { rcsb_entry_info { experimental_method resolution_combined } }
61
+ } }`
62
+
63
+ // RCSB's cap on ids per batch request
64
+ const DETAILS_BATCH = 1000
65
+
31
66
  function isRecord(value: unknown): value is Record<string, unknown> {
32
67
  return typeof value === 'object' && value !== null && !Array.isArray(value)
33
68
  }
@@ -36,77 +71,166 @@ function finiteNumber(value: unknown) {
36
71
  return typeof value === 'number' && Number.isFinite(value) ? value : undefined
37
72
  }
38
73
 
74
+ function strings(value: unknown) {
75
+ return Array.isArray(value)
76
+ ? value.filter((v): v is string => typeof v === 'string')
77
+ : []
78
+ }
79
+
39
80
  /**
40
- * PDBe entries only, best-covering first and sharpest within a tie.
41
- *
42
- * Predicted entries (AlphaFold, SWISS-MODEL, AlphaFill) are left out because a
43
- * prediction comes from AlphaFold's own API, which answers per model. And
44
- * 3D-Beacons files SASBDB's small-angle-scattering fits under "experimentally
45
- * determined" too, with numeric ids and near-full coverage — dystrophin's best
46
- * "structure" by coverage was SASBDB 436 — which are not entries a residue
47
- * mapping exists for. Hence the `provider === 'PDBe'` gate rather than the
48
- * category alone.
81
+ * PDB entities only: the same list carries RCSB's computed models
82
+ * (`AF_AFP04637F1_1`), which a residue mapping does not exist for. The range
83
+ * spans every aligned region, as 3D-Beacons reported it.
49
84
  */
50
- export function parseExperimentalStructures(
51
- json: unknown,
52
- ): ExperimentalStructure[] {
53
- const structures = isRecord(json) ? json.structures : undefined
54
- if (!Array.isArray(structures)) {
85
+ export function parseEntityAlignments(data: unknown): EntityAlignment[] {
86
+ const alignments = isRecord(data) ? data.alignments : undefined
87
+ const targets = isRecord(alignments)
88
+ ? alignments.target_alignments
89
+ : undefined
90
+ if (!Array.isArray(targets)) {
55
91
  return []
56
92
  }
57
- return structures
58
- .flatMap(entry => {
59
- const s = isRecord(entry) ? entry.summary : undefined
60
- if (!isRecord(s)) {
61
- return []
62
- }
63
- const start = finiteNumber(s.uniprot_start)
64
- const end = finiteNumber(s.uniprot_end)
65
- const coverage = finiteNumber(s.coverage)
66
- return typeof s.model_identifier === 'string' &&
67
- s.provider === 'PDBe' &&
68
- s.model_category === 'EXPERIMENTALLY DETERMINED' &&
69
- start !== undefined &&
70
- end !== undefined &&
71
- coverage !== undefined
72
- ? [
73
- {
74
- pdbId: s.model_identifier.toLowerCase(),
75
- method:
76
- typeof s.experimental_method === 'string'
77
- ? s.experimental_method
78
- : 'experimental',
79
- resolution: finiteNumber(s.resolution),
80
- start,
81
- end,
82
- coverage,
83
- },
84
- ]
85
- : []
93
+ return targets.flatMap(target => {
94
+ if (!isRecord(target) || typeof target.target_id !== 'string') {
95
+ return []
96
+ }
97
+ const [pdbId] = target.target_id.split('_')
98
+ const coverage = isRecord(target.coverage)
99
+ ? finiteNumber(target.coverage.query_coverage)
100
+ : undefined
101
+ const regions = Array.isArray(target.aligned_regions)
102
+ ? target.aligned_regions.filter(isRecord)
103
+ : []
104
+ const begins = regions.map(r => finiteNumber(r.query_begin))
105
+ const ends = regions.map(r => finiteNumber(r.query_end))
106
+ return pdbId !== undefined &&
107
+ isPdbId(pdbId) &&
108
+ coverage !== undefined &&
109
+ begins.length > 0 &&
110
+ begins.every(b => b !== undefined) &&
111
+ ends.every(e => e !== undefined)
112
+ ? [
113
+ {
114
+ entityId: target.target_id,
115
+ pdbId: pdbId.toLowerCase(),
116
+ start: Math.min(...begins),
117
+ end: Math.max(...ends),
118
+ coverage,
119
+ },
120
+ ]
121
+ : []
122
+ })
123
+ }
124
+
125
+ export function parseEntityDetails(data: unknown): Map<string, EntityDetails> {
126
+ const entities = isRecord(data) ? data.polymer_entities : undefined
127
+ const details = new Map<string, EntityDetails>()
128
+ for (const entity of Array.isArray(entities) ? entities : []) {
129
+ if (!isRecord(entity) || typeof entity.rcsb_id !== 'string') {
130
+ continue
131
+ }
132
+ const ids = entity.rcsb_polymer_entity_container_identifiers
133
+ const info = isRecord(entity.entry)
134
+ ? entity.entry.rcsb_entry_info
135
+ : undefined
136
+ const resolutions = isRecord(info) ? info.resolution_combined : undefined
137
+ const resolution = Array.isArray(resolutions)
138
+ ? finiteNumber(resolutions[0])
139
+ : undefined
140
+ details.set(entity.rcsb_id, {
141
+ method:
142
+ isRecord(info) && typeof info.experimental_method === 'string'
143
+ ? info.experimental_method
144
+ : 'experimental',
145
+ ...(resolution === undefined ? {} : { resolution }),
146
+ chains: strings(isRecord(ids) ? ids.auth_asym_ids : undefined),
86
147
  })
87
- .sort(
88
- (a, b) =>
89
- b.coverage - a.coverage ||
90
- (a.resolution ?? Infinity) - (b.resolution ?? Infinity),
91
- )
148
+ }
149
+ return details
150
+ }
151
+
152
+ /**
153
+ * One row per entry, best coverage first and sharpest within a tie. An entry
154
+ * with several entities of the protein (a construct crystallised beside a
155
+ * different fragment of itself) takes the best-covering entity's range and
156
+ * every entity's chains.
157
+ */
158
+ export function experimentalStructures(
159
+ alignments: EntityAlignment[],
160
+ details: ReadonlyMap<string, EntityDetails>,
161
+ ): ExperimentalStructure[] {
162
+ const byEntry = new Map<string, ExperimentalStructure>()
163
+ for (const a of alignments) {
164
+ const d = details.get(a.entityId)
165
+ if (!d) {
166
+ continue
167
+ }
168
+ const existing = byEntry.get(a.pdbId)
169
+ if (!existing) {
170
+ byEntry.set(a.pdbId, {
171
+ pdbId: a.pdbId,
172
+ method: d.method,
173
+ ...(d.resolution === undefined ? {} : { resolution: d.resolution }),
174
+ start: a.start,
175
+ end: a.end,
176
+ coverage: a.coverage,
177
+ chains: [...d.chains],
178
+ })
179
+ continue
180
+ }
181
+ for (const chain of d.chains) {
182
+ if (!existing.chains.includes(chain)) {
183
+ existing.chains.push(chain)
184
+ }
185
+ }
186
+ if (a.coverage > existing.coverage) {
187
+ Object.assign(existing, {
188
+ start: a.start,
189
+ end: a.end,
190
+ coverage: a.coverage,
191
+ })
192
+ }
193
+ }
194
+ return [...byEntry.values()].sort(
195
+ (a, b) =>
196
+ b.coverage - a.coverage ||
197
+ (a.resolution ?? Infinity) - (b.resolution ?? Infinity),
198
+ )
92
199
  }
93
200
 
94
- /** Best-effort: an unreachable API reads as "no entries", which costs a
95
- * structure suggestion and nothing else. */
201
+ const ISOFORM_SUFFIX = /-\d+$/
202
+
203
+ /**
204
+ * Every PDB entry aligned to the accession. An accession with none, or one
205
+ * RCSB does not know, is `[]`; an isoform accession is asked as its entry,
206
+ * which is what the alignment is numbered on.
207
+ */
96
208
  export async function fetchExperimentalStructures(
97
209
  uniprotId: string,
98
210
  opts?: FetchOptions,
99
211
  ): Promise<ExperimentalStructure[]> {
100
- for (let attempt = 0; attempt < 2; attempt++) {
101
- const res = await rawfetch(beaconsSummaryUrl(uniprotId), opts).catch(
102
- () => undefined,
212
+ const alignments = parseEntityAlignments(
213
+ await graphqlFetch(
214
+ RCSB_SEQUENCE_COORDINATES_URL,
215
+ entityAlignmentsQuery(uniprotId.replace(ISOFORM_SUFFIX, '')),
216
+ undefined,
217
+ opts,
218
+ ),
219
+ )
220
+ const ids = alignments.map(a => a.entityId)
221
+ const details = new Map<string, EntityDetails>()
222
+ for (let i = 0; i < ids.length; i += DETAILS_BATCH) {
223
+ const batch = parseEntityDetails(
224
+ await graphqlFetch(
225
+ RCSB_DATA_GRAPHQL_URL,
226
+ ENTITY_DETAILS_QUERY,
227
+ { ids: ids.slice(i, i + DETAILS_BATCH) },
228
+ opts,
229
+ ),
103
230
  )
104
- if (res?.ok) {
105
- return parseExperimentalStructures(await res.json())
106
- }
107
- if (res?.status === 404 || opts?.signal?.aborted) {
108
- return []
231
+ for (const [id, d] of batch) {
232
+ details.set(id, d)
109
233
  }
110
234
  }
111
- return []
235
+ return experimentalStructures(alignments, details)
112
236
  }
@@ -37,6 +37,17 @@ export function getPdbStructureUrl(pdbId: string) {
37
37
  return `https://files.rcsb.org/download/${pdbId}.cif`
38
38
  }
39
39
 
40
+ // A PDB id is four characters, the first a digit: 1TUP, 6ZIO. Checked before
41
+ // a typed one reaches a fetch, so three characters on the way to four are not
42
+ // a 404 apiece.
43
+ export function isPdbId(value: string) {
44
+ return /^[1-9][A-Za-z0-9]{3}$/.test(value)
45
+ }
46
+
47
+ export function rcsbEntryUrl(pdbId: string) {
48
+ return `https://www.rcsb.org/structure/${pdbId.toUpperCase()}`
49
+ }
50
+
40
51
  /**
41
52
  * Resolve the `{ pdbId }` shorthand to a concrete structure URL, so a
42
53
  * hand-authored snapshot or a short launch URL doesn't have to know RCSB's
@@ -1,23 +0,0 @@
1
- export interface PdbStructureEntry {
2
- pdbId: string;
3
- experimentalMethod: string;
4
- /** Å; absent for NMR and some EM entries */
5
- resolution?: number;
6
- /** 1-based inclusive UniProt positions the entry covers */
7
- unpStart: number;
8
- unpEnd: number;
9
- /** share of the UniProt sequence the entry covers, 0..1 */
10
- coverage: number;
11
- chains: string[];
12
- }
13
- export declare function pdbeBestStructuresUrl(uniprotId: string): string;
14
- export declare function isPdbId(value: string): boolean;
15
- export declare function rcsbEntryUrl(pdbId: string): string;
16
- /**
17
- * Groups the per-chain rows into one entry per PDB id, keeping PDBe's order.
18
- * An entry whose chains cover different UniProt ranges (a construct
19
- * crystallised beside a different fragment of itself) keeps its first row's
20
- * range, which is the one PDBe ranked it by. The response is keyed by
21
- * accession, so the single entry is taken whatever its key.
22
- */
23
- export declare function parseBestStructures(json: unknown): PdbStructureEntry[];
@@ -1,79 +0,0 @@
1
- "use strict";
2
- // PDBe's SIFTS-derived list of the experimental structures covering a UniProt
3
- // entry, one row per chain, ranked by PDBe (coverage, then resolution). The
4
- // dialog shows one row per entry, so the chains of a homomer collapse into it.
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.pdbeBestStructuresUrl = pdbeBestStructuresUrl;
7
- exports.isPdbId = isPdbId;
8
- exports.rcsbEntryUrl = rcsbEntryUrl;
9
- exports.parseBestStructures = parseBestStructures;
10
- function pdbeBestStructuresUrl(uniprotId) {
11
- return `https://www.ebi.ac.uk/pdbe/api/mappings/best_structures/${uniprotId}`;
12
- }
13
- // A PDB id is four characters, the first a digit: 1TUP, 6ZIO. Checked before
14
- // a typed one reaches a fetch, so three characters on the way to four are not
15
- // a 404 apiece.
16
- function isPdbId(value) {
17
- return /^[1-9][A-Za-z0-9]{3}$/.test(value);
18
- }
19
- function rcsbEntryUrl(pdbId) {
20
- return `https://www.rcsb.org/structure/${pdbId.toUpperCase()}`;
21
- }
22
- function isRecord(value) {
23
- return typeof value === 'object' && value !== null && !Array.isArray(value);
24
- }
25
- function finiteNumber(value) {
26
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
27
- }
28
- /**
29
- * Groups the per-chain rows into one entry per PDB id, keeping PDBe's order.
30
- * An entry whose chains cover different UniProt ranges (a construct
31
- * crystallised beside a different fragment of itself) keeps its first row's
32
- * range, which is the one PDBe ranked it by. The response is keyed by
33
- * accession, so the single entry is taken whatever its key.
34
- */
35
- function parseBestStructures(json) {
36
- const rows = isRecord(json) ? Object.values(json)[0] : undefined;
37
- if (!Array.isArray(rows)) {
38
- return [];
39
- }
40
- const byId = new Map();
41
- for (const row of rows) {
42
- if (!isRecord(row)) {
43
- continue;
44
- }
45
- const pdbId = typeof row.pdb_id === 'string' ? row.pdb_id : undefined;
46
- const chain = typeof row.chain_id === 'string' ? row.chain_id : undefined;
47
- const unpStart = finiteNumber(row.unp_start);
48
- const unpEnd = finiteNumber(row.unp_end);
49
- const coverage = finiteNumber(row.coverage);
50
- if (pdbId === undefined ||
51
- chain === undefined ||
52
- unpStart === undefined ||
53
- unpEnd === undefined ||
54
- coverage === undefined) {
55
- continue;
56
- }
57
- const existing = byId.get(pdbId);
58
- if (existing) {
59
- if (!existing.chains.includes(chain)) {
60
- existing.chains.push(chain);
61
- }
62
- continue;
63
- }
64
- const resolution = finiteNumber(row.resolution);
65
- byId.set(pdbId, {
66
- pdbId,
67
- experimentalMethod: typeof row.experimental_method === 'string'
68
- ? row.experimental_method
69
- : 'unknown',
70
- ...(resolution === undefined ? {} : { resolution }),
71
- unpStart,
72
- unpEnd,
73
- coverage,
74
- chains: [chain],
75
- });
76
- }
77
- return [...byId.values()];
78
- }
79
- //# sourceMappingURL=pdbeBestStructures.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pdbeBestStructures.js","sourceRoot":"","sources":["../src/pdbeBestStructures.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,4EAA4E;AAC5E,+EAA+E;;AAe/E,sDAEC;AAKD,0BAEC;AAED,oCAEC;AAiBD,kDA8CC;AA5ED,SAAgB,qBAAqB,CAAC,SAAiB;IACrD,OAAO,2DAA2D,SAAS,EAAE,CAAA;AAC/E,CAAC;AAED,6EAA6E;AAC7E,8EAA8E;AAC9E,gBAAgB;AAChB,SAAgB,OAAO,CAAC,KAAa;IACnC,OAAO,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AAC5C,CAAC;AAED,SAAgB,YAAY,CAAC,KAAa;IACxC,OAAO,kCAAkC,KAAK,CAAC,WAAW,EAAE,EAAE,CAAA;AAChE,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAA;AAChF,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,mBAAmB,CAAC,IAAa;IAC/C,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAChE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACzB,OAAO,EAAE,CAAA;IACX,CAAC;IACD,MAAM,IAAI,GAAG,IAAI,GAAG,EAA6B,CAAA;IACjD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YACnB,SAAQ;QACV,CAAC;QACD,MAAM,KAAK,GAAG,OAAO,GAAG,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;QACrE,MAAM,KAAK,GAAG,OAAO,GAAG,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAA;QACzE,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;QAC5C,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,OAAO,CAAC,CAAA;QACxC,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;QAC3C,IACE,KAAK,KAAK,SAAS;YACnB,KAAK,KAAK,SAAS;YACnB,QAAQ,KAAK,SAAS;YACtB,MAAM,KAAK,SAAS;YACpB,QAAQ,KAAK,SAAS,EACtB,CAAC;YACD,SAAQ;QACV,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,QAAQ,EAAE,CAAC;YACb,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;gBACrC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAC7B,CAAC;YACD,SAAQ;QACV,CAAC;QACD,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,UAAU,CAAC,CAAA;QAC/C,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE;YACd,KAAK;YACL,kBAAkB,EAChB,OAAO,GAAG,CAAC,mBAAmB,KAAK,QAAQ;gBACzC,CAAC,CAAC,GAAG,CAAC,mBAAmB;gBACzB,CAAC,CAAC,SAAS;YACf,GAAG,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC;YACnD,QAAQ;YACR,MAAM;YACN,QAAQ;YACR,MAAM,EAAE,CAAC,KAAK,CAAC;SAChB,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAA;AAC3B,CAAC"}
@@ -1,7 +0,0 @@
1
- export declare function pdbeEntryMoleculesUrl(pdbId: string): string;
2
- /**
3
- * One sequence per protein entity, in entity order. Nucleic-acid entities are
4
- * left out: A, C, G, T and U are amino-acid letters too, so a DNA chain scores
5
- * as a candidate rather than losing honestly.
6
- */
7
- export declare function parseEntryMolecules(json: unknown): string[];
@@ -1,39 +0,0 @@
1
- "use strict";
2
- // The polymer entities of a PDB entry, as PDBe already knows them. The dialog
3
- // wants the entry's residues only to annotate the isoform list, and reading
4
- // them out of the structure file meant downloading and parsing the whole
5
- // mmCIF — minutes for a ribosome, for a label. This is a few kB of JSON, and
6
- // the view downloads the file itself when the user actually launches.
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.pdbeEntryMoleculesUrl = pdbeEntryMoleculesUrl;
9
- exports.parseEntryMolecules = parseEntryMolecules;
10
- function pdbeEntryMoleculesUrl(pdbId) {
11
- return `https://www.ebi.ac.uk/pdbe/api/pdb/entry/molecules/${pdbId.toLowerCase()}`;
12
- }
13
- function isRecord(value) {
14
- return typeof value === 'object' && value !== null && !Array.isArray(value);
15
- }
16
- /**
17
- * One sequence per protein entity, in entity order. Nucleic-acid entities are
18
- * left out: A, C, G, T and U are amino-acid letters too, so a DNA chain scores
19
- * as a candidate rather than losing honestly.
20
- */
21
- function parseEntryMolecules(json) {
22
- const entities = isRecord(json) ? Object.values(json)[0] : undefined;
23
- if (!Array.isArray(entities)) {
24
- return [];
25
- }
26
- return entities.flatMap(entity => {
27
- if (!isRecord(entity)) {
28
- return [];
29
- }
30
- const { molecule_type: type, sequence } = entity;
31
- return typeof type === 'string' &&
32
- type.startsWith('polypeptide') &&
33
- typeof sequence === 'string' &&
34
- sequence.length > 0
35
- ? [sequence]
36
- : [];
37
- });
38
- }
39
- //# sourceMappingURL=pdbeEntryMolecules.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pdbeEntryMolecules.js","sourceRoot":"","sources":["../src/pdbeEntryMolecules.ts"],"names":[],"mappings":";AAAA,8EAA8E;AAC9E,4EAA4E;AAC5E,yEAAyE;AACzE,6EAA6E;AAC7E,sEAAsE;;AAEtE,sDAEC;AAWD,kDAiBC;AA9BD,SAAgB,qBAAqB,CAAC,KAAa;IACjD,OAAO,sDAAsD,KAAK,CAAC,WAAW,EAAE,EAAE,CAAA;AACpF,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED;;;;GAIG;AACH,SAAgB,mBAAmB,CAAC,IAAa;IAC/C,MAAM,QAAQ,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IACpE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAA;IACX,CAAC;IACD,OAAO,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE;QAC/B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACtB,OAAO,EAAE,CAAA;QACX,CAAC;QACD,MAAM,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,MAAM,CAAA;QAChD,OAAO,OAAO,IAAI,KAAK,QAAQ;YAC7B,IAAI,CAAC,UAAU,CAAC,aAAa,CAAC;YAC9B,OAAO,QAAQ,KAAK,QAAQ;YAC5B,QAAQ,CAAC,MAAM,GAAG,CAAC;YACnB,CAAC,CAAC,CAAC,QAAQ,CAAC;YACZ,CAAC,CAAC,EAAE,CAAA;IACR,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -1,23 +0,0 @@
1
- import type { UniProtStructureSegment } from './pdbUniProtMapping.ts';
2
- export interface AuthorRange {
3
- start: number;
4
- end: number;
5
- chain: string;
6
- /** author number minus UniProt number in the chosen segment */
7
- shift: number;
8
- }
9
- /** The segments SIFTS gives one accession, chain ids and author numbers
10
- * included, out of a whole entry's mappings. */
11
- export declare function segmentsForAccession(mappings: readonly {
12
- accession: string;
13
- segments: UniProtStructureSegment[];
14
- }[], accession: string): UniProtStructureSegment[];
15
- /**
16
- * The author-numbered range a UniProt range is cited by in the entry: the
17
- * segment overlapping most of the range decides the shift. Undefined when no
18
- * chain of the entry covers any of it, or when SIFTS named no author numbers.
19
- */
20
- export declare function toAuthorRange(segments: readonly UniProtStructureSegment[], range: {
21
- start: number;
22
- end: number;
23
- }): AuthorRange | undefined;
@@ -1,44 +0,0 @@
1
- "use strict";
2
- // How a PDB entry numbers the residues of a UniProt sequence. Author numbering
3
- // is what a paper cites and what a viewer lights a named residue by, and it is
4
- // not the UniProt numbering for every entry: haemoglobin's chains number from
5
- // the mature protein, so author residue 1 is UniProt residue 2 (2HHB, measured
6
- // 2026-09-12). SIFTS records the correspondence per chain segment, and
7
- // `fetchUniProtStructureMappings` is what asks PDBe for it.
8
- Object.defineProperty(exports, "__esModule", { value: true });
9
- exports.segmentsForAccession = segmentsForAccession;
10
- exports.toAuthorRange = toAuthorRange;
11
- /** The segments SIFTS gives one accession, chain ids and author numbers
12
- * included, out of a whole entry's mappings. */
13
- function segmentsForAccession(mappings, accession) {
14
- return mappings.find(m => m.accession === accession)?.segments ?? [];
15
- }
16
- /**
17
- * The author-numbered range a UniProt range is cited by in the entry: the
18
- * segment overlapping most of the range decides the shift. Undefined when no
19
- * chain of the entry covers any of it, or when SIFTS named no author numbers.
20
- */
21
- function toAuthorRange(segments, range) {
22
- const overlap = (s) => Math.min(s.unpEnd, range.end) - Math.max(s.unpStart, range.start) + 1;
23
- const best = segments
24
- .flatMap(s => {
25
- const { chainId, authorStart, unpStart, unpEnd } = s;
26
- return chainId !== undefined &&
27
- authorStart !== undefined &&
28
- overlap(s) > 0
29
- ? [{ chainId, authorStart, unpStart, unpEnd, overlap: overlap(s) }]
30
- : [];
31
- })
32
- .toSorted((a, b) => b.overlap - a.overlap)[0];
33
- if (!best) {
34
- return undefined;
35
- }
36
- const shift = best.authorStart - best.unpStart;
37
- return {
38
- start: Math.max(range.start, best.unpStart) + shift,
39
- end: Math.min(range.end, best.unpEnd) + shift,
40
- chain: best.chainId,
41
- shift,
42
- };
43
- }
44
- //# sourceMappingURL=siftsNumbering.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"siftsNumbering.js","sourceRoot":"","sources":["../src/siftsNumbering.ts"],"names":[],"mappings":";AAAA,+EAA+E;AAC/E,+EAA+E;AAC/E,8EAA8E;AAC9E,+EAA+E;AAC/E,uEAAuE;AACvE,4DAA4D;;AAc5D,oDAQC;AAOD,sCA0BC;AA3CD;gDACgD;AAChD,SAAgB,oBAAoB,CAClC,QAGG,EACH,SAAiB;IAEjB,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,KAAK,SAAS,CAAC,EAAE,QAAQ,IAAI,EAAE,CAAA;AACtE,CAAC;AAED;;;;GAIG;AACH,SAAgB,aAAa,CAC3B,QAA4C,EAC5C,KAAqC;IAErC,MAAM,OAAO,GAAG,CAAC,CAA0B,EAAE,EAAE,CAC7C,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,QAAQ,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IACvE,MAAM,IAAI,GAAG,QAAQ;SAClB,OAAO,CAAC,CAAC,CAAC,EAAE;QACX,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,CAAC,CAAA;QACpD,OAAO,OAAO,KAAK,SAAS;YAC1B,WAAW,KAAK,SAAS;YACzB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;YACd,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;YACnE,CAAC,CAAC,EAAE,CAAA;IACR,CAAC,CAAC;SACD,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAA;IAC/C,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC,QAAQ,CAAA;IAC9C,OAAO;QACL,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,KAAK;QACnD,GAAG,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,KAAK;QAC7C,KAAK,EAAE,IAAI,CAAC,OAAO;QACnB,KAAK;KACN,CAAA;AACH,CAAC"}
@@ -1,23 +0,0 @@
1
- export interface PdbStructureEntry {
2
- pdbId: string;
3
- experimentalMethod: string;
4
- /** Å; absent for NMR and some EM entries */
5
- resolution?: number;
6
- /** 1-based inclusive UniProt positions the entry covers */
7
- unpStart: number;
8
- unpEnd: number;
9
- /** share of the UniProt sequence the entry covers, 0..1 */
10
- coverage: number;
11
- chains: string[];
12
- }
13
- export declare function pdbeBestStructuresUrl(uniprotId: string): string;
14
- export declare function isPdbId(value: string): boolean;
15
- export declare function rcsbEntryUrl(pdbId: string): string;
16
- /**
17
- * Groups the per-chain rows into one entry per PDB id, keeping PDBe's order.
18
- * An entry whose chains cover different UniProt ranges (a construct
19
- * crystallised beside a different fragment of itself) keeps its first row's
20
- * range, which is the one PDBe ranked it by. The response is keyed by
21
- * accession, so the single entry is taken whatever its key.
22
- */
23
- export declare function parseBestStructures(json: unknown): PdbStructureEntry[];
@@ -1,73 +0,0 @@
1
- // PDBe's SIFTS-derived list of the experimental structures covering a UniProt
2
- // entry, one row per chain, ranked by PDBe (coverage, then resolution). The
3
- // dialog shows one row per entry, so the chains of a homomer collapse into it.
4
- export function pdbeBestStructuresUrl(uniprotId) {
5
- return `https://www.ebi.ac.uk/pdbe/api/mappings/best_structures/${uniprotId}`;
6
- }
7
- // A PDB id is four characters, the first a digit: 1TUP, 6ZIO. Checked before
8
- // a typed one reaches a fetch, so three characters on the way to four are not
9
- // a 404 apiece.
10
- export function isPdbId(value) {
11
- return /^[1-9][A-Za-z0-9]{3}$/.test(value);
12
- }
13
- export function rcsbEntryUrl(pdbId) {
14
- return `https://www.rcsb.org/structure/${pdbId.toUpperCase()}`;
15
- }
16
- function isRecord(value) {
17
- return typeof value === 'object' && value !== null && !Array.isArray(value);
18
- }
19
- function finiteNumber(value) {
20
- return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
21
- }
22
- /**
23
- * Groups the per-chain rows into one entry per PDB id, keeping PDBe's order.
24
- * An entry whose chains cover different UniProt ranges (a construct
25
- * crystallised beside a different fragment of itself) keeps its first row's
26
- * range, which is the one PDBe ranked it by. The response is keyed by
27
- * accession, so the single entry is taken whatever its key.
28
- */
29
- export function parseBestStructures(json) {
30
- const rows = isRecord(json) ? Object.values(json)[0] : undefined;
31
- if (!Array.isArray(rows)) {
32
- return [];
33
- }
34
- const byId = new Map();
35
- for (const row of rows) {
36
- if (!isRecord(row)) {
37
- continue;
38
- }
39
- const pdbId = typeof row.pdb_id === 'string' ? row.pdb_id : undefined;
40
- const chain = typeof row.chain_id === 'string' ? row.chain_id : undefined;
41
- const unpStart = finiteNumber(row.unp_start);
42
- const unpEnd = finiteNumber(row.unp_end);
43
- const coverage = finiteNumber(row.coverage);
44
- if (pdbId === undefined ||
45
- chain === undefined ||
46
- unpStart === undefined ||
47
- unpEnd === undefined ||
48
- coverage === undefined) {
49
- continue;
50
- }
51
- const existing = byId.get(pdbId);
52
- if (existing) {
53
- if (!existing.chains.includes(chain)) {
54
- existing.chains.push(chain);
55
- }
56
- continue;
57
- }
58
- const resolution = finiteNumber(row.resolution);
59
- byId.set(pdbId, {
60
- pdbId,
61
- experimentalMethod: typeof row.experimental_method === 'string'
62
- ? row.experimental_method
63
- : 'unknown',
64
- ...(resolution === undefined ? {} : { resolution }),
65
- unpStart,
66
- unpEnd,
67
- coverage,
68
- chains: [chain],
69
- });
70
- }
71
- return [...byId.values()];
72
- }
73
- //# sourceMappingURL=pdbeBestStructures.js.map