@@ -28,77 +28,85 @@ export async function persistPackagistMetadata(
2828 const { versionRows, latestVersion, firstReleaseAt, latestReleaseAt, licenses, homepage } =
2929 buildPackagistVersionRows ( expanded )
3030
31- const agg = await updatePackagistVersionAggregates ( qx , purl , {
32- versionsCount : versionRows . length ,
33- latestVersion,
34- firstReleaseAt,
35- latestReleaseAt,
36- licenses,
37- homepage,
38- } )
31+ let found = false
32+ const changedFields : string [ ] = [ ]
33+ let unresolvedDependencyTargets = 0
3934
40- if ( ! agg ) {
41- return { found : false , changedFields : [ ] , unresolvedDependencyTargets : 0 }
42- }
35+ // One transaction for the whole p2 write path: the aggregates update, version
36+ // upsert + is_latest cleanup, and dependency reconcile (delete stale + upsert
37+ // current) are each multi-statement on their own — sharing one tx means a failure
38+ // partway through can never leave versions/dependencies half-refreshed.
39+ await qx . tx ( async ( t ) => {
40+ const agg = await updatePackagistVersionAggregates ( t , purl , {
41+ versionsCount : versionRows . length ,
42+ latestVersion,
43+ firstReleaseAt,
44+ latestReleaseAt,
45+ licenses,
46+ homepage,
47+ } )
4348
44- const changedFields = [ ... agg . changedFields ]
49+ if ( ! agg ) return
4550
46- let versionIds : Array < { number : string ; id : string } > = [ ]
47- if ( versionRows . length > 0 ) {
48- const versionResult = await upsertPackagistVersions ( qx , agg . id , versionRows , latestVersion )
49- changedFields . push ( ...versionResult . changedFields )
50- versionIds = versionResult . versionIds
51- }
51+ found = true
52+ changedFields . push ( ...agg . changedFields )
5253
53- // One pass over the tagged versions collects target names and provisional edges;
54- // target ids are then resolved in a single batch query.
55- const versionMap = new Map ( versionIds . map ( ( v ) => [ v . number , v . id ] ) )
56- const targetNames = new Set < string > ( )
57- const pending : Array < { versionId : string ; dep : PackagistDependency } > = [ ]
54+ let versionIds : Array < { number : string ; id : string } > = [ ]
55+ if ( versionRows . length > 0 ) {
56+ const versionResult = await upsertPackagistVersions ( t , agg . id , versionRows , latestVersion )
57+ changedFields . push ( ...versionResult . changedFields )
58+ versionIds = versionResult . versionIds
59+ }
5860
59- for ( const v of expanded ) {
60- if ( isPackagistDevVersion ( v . version , v . version_normalized ) ) continue
61- const versionId = versionMap . get ( v . version )
62- if ( ! versionId ) continue
61+ // One pass over the tagged versions collects target names and provisional edges;
62+ // target ids are then resolved in a single batch query.
63+ const versionMap = new Map ( versionIds . map ( ( v ) => [ v . number , v . id ] ) )
64+ const targetNames = new Set < string > ( )
65+ const pending : Array < { versionId : string ; dep : PackagistDependency } > = [ ]
6366
64- for ( const dep of extractVersionDependencies ( v ) ) {
65- targetNames . add ( dep . name )
66- pending . push ( { versionId, dep } )
67+ for ( const v of expanded ) {
68+ if ( isPackagistDevVersion ( v . version , v . version_normalized ) ) continue
69+ const versionId = versionMap . get ( v . version )
70+ if ( ! versionId ) continue
71+
72+ for ( const dep of extractVersionDependencies ( v ) ) {
73+ targetNames . add ( dep . name )
74+ pending . push ( { versionId, dep } )
75+ }
6776 }
68- }
6977
70- // Reconciled for every version being refreshed (not just ones with a resolved
71- // dependency this pass) — a version whose manifest drops a requirement, or all of
72- // them, must have its stale package_dependencies rows removed, not just the newly
73- // declared ones upserted.
74- let unresolvedDependencyTargets = 0
75- if ( versionIds . length > 0 ) {
76- const edges : VersionDependencyEdge [ ] = [ ]
77- if ( pending . length > 0 ) {
78- const idMap = await getPackagistPackageIdsByNames ( qx , Array . from ( targetNames ) )
79- for ( const { versionId, dep } of pending ) {
80- const dependsOnId = idMap . get ( dep . name )
81- if ( ! dependsOnId ) {
82- unresolvedDependencyTargets ++
83- continue
78+ // Reconciled for every version being refreshed (not just ones with a resolved
79+ // dependency this pass) — a version whose manifest drops a requirement, or all of
80+ // them, must have its stale package_dependencies rows removed, not just the newly
81+ // declared ones upserted.
82+ if ( versionIds . length > 0 ) {
83+ const edges : VersionDependencyEdge [ ] = [ ]
84+ if ( pending . length > 0 ) {
85+ const idMap = await getPackagistPackageIdsByNames ( t , Array . from ( targetNames ) )
86+ for ( const { versionId, dep } of pending ) {
87+ const dependsOnId = idMap . get ( dep . name )
88+ if ( ! dependsOnId ) {
89+ unresolvedDependencyTargets ++
90+ continue
91+ }
92+ edges . push ( {
93+ packageId : agg . id ,
94+ versionId,
95+ dependsOnId,
96+ constraint : dep . constraint ,
97+ kind : dep . kind ,
98+ } )
8499 }
85- edges . push ( {
86- packageId : agg . id ,
87- versionId,
88- dependsOnId,
89- constraint : dep . constraint ,
90- kind : dep . kind ,
91- } )
92100 }
93- }
94101
95- const depChanges = await reconcileVersionDependencies (
96- qx ,
97- versionIds . map ( ( v ) => v . id ) ,
98- edges ,
99- )
100- changedFields . push ( ...depChanges )
101- }
102+ const depChanges = await reconcileVersionDependencies (
103+ t ,
104+ versionIds . map ( ( v ) => v . id ) ,
105+ edges ,
106+ )
107+ changedFields . push ( ...depChanges )
108+ }
109+ } )
102110
103- return { found : true , changedFields, unresolvedDependencyTargets }
111+ return { found, changedFields, unresolvedDependencyTargets }
104112}
0 commit comments